1 package trustanchor
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 corev1 "k8s.io/api/core/v1"
13 "k8s.io/apimachinery/pkg/api/errors"
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 "k8s.io/apimachinery/pkg/types"
16 "sigs.k8s.io/controller-runtime/pkg/client"
17
18 "edge-infra.dev/pkg/edge/linkerd"
19 l5dv1alpha1 "edge-infra.dev/pkg/edge/linkerd/k8s/apis/linkerd/v1alpha1"
20 "edge-infra.dev/test/f2"
21 "edge-infra.dev/test/f2/x/ktest"
22 )
23
24 var f f2.Framework
25
26 var linkerdNamespace = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: linkerd.Namespace}}
27
28 func TestMain(m *testing.M) {
29 f = f2.New(context.Background(),
30 f2.WithExtensions(
31 ktest.New(),
32 )).
33 Setup(func(ctx f2.Context) (f2.Context, error) {
34 k, err := ktest.FromContext(ctx)
35 if err != nil {
36 return ctx, err
37 }
38
39 if !*k.Env.UseExistingCluster {
40 k.Timeout = 5 * time.Second
41 k.Tick = 10 * time.Millisecond
42 }
43 return ctx, nil
44 }).Teardown()
45 os.Exit(f.Run(m))
46 }
47
48 func TestCreateIfNotExists(t *testing.T) {
49 var (
50 k *ktest.K8s
51 l5d *l5dv1alpha1.Linkerd
52 emptyTrustAnchorSecret *corev1.Secret
53 trustAnchorSecret *corev1.Secret
54 )
55 feature := f2.NewFeature("CreateIfNotExists").
56 Setup("create linkerd resources", func(ctx f2.Context, t *testing.T) f2.Context {
57 k = ktest.FromContextT(ctx, t)
58 l5d = createL5d()
59 emptyTrustAnchorSecret = buildSecret(l5d, []byte{}, []byte{})
60 trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
61 return ctx
62 }).
63 Test("unable to create trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
64 cert, err := CreateIfNotExists(ctx, k.Client, l5d)
65 assert.EqualError(t, err, "failed to create secret")
66 assert.Equal(t, "", cert)
67 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionFalse)
68 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.TrustAnchorSecretSetupFailedReason)
69 return ctx
70 }).
71 Test("unable to determine if trust anchor exists", func(ctx f2.Context, t *testing.T) f2.Context {
72 require.NoError(t, k.Client.Create(ctx, linkerdNamespace))
73 require.NoError(t, k.Client.Create(ctx, emptyTrustAnchorSecret))
74 cert, err := CreateIfNotExists(ctx, k.Client, l5d)
75 assert.Error(t, err)
76 assert.Equal(t, "", cert)
77 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionFalse)
78 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.TrustAnchorSecretSetupFailedReason)
79 return ctx
80 }).
81 Test("successfully create new certificate", func(ctx f2.Context, t *testing.T) f2.Context {
82 require.NoError(t, k.Client.Delete(ctx, emptyTrustAnchorSecret))
83 cert, err := CreateIfNotExists(ctx, k.Client, l5d)
84 assert.NoError(t, err)
85 assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
86 assert.Contains(t, cert, "-----END CERTIFICATE-----")
87 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
88 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
89 return ctx
90 }).
91 Test("succesfullly return existing certificate", func(ctx f2.Context, t *testing.T) f2.Context {
92 cert, err := CreateIfNotExists(ctx, k.Client, l5d)
93 assert.NoError(t, err)
94 assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
95 assert.Contains(t, cert, "-----END CERTIFICATE-----")
96 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
97 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
98 return ctx
99 }).
100 Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
101 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
102 return ctx
103 }).Feature()
104 f.Test(t, feature)
105 }
106
107 func TestSecretExists(t *testing.T) {
108 var (
109 k *ktest.K8s
110 l5d *l5dv1alpha1.Linkerd
111 emptyTrustAnchorSecret *corev1.Secret
112 trustAnchorSecret *corev1.Secret
113 )
114 feature := f2.NewFeature("secretExists").
115 Setup("create linkerd resources", func(ctx f2.Context, t *testing.T) f2.Context {
116 k = ktest.FromContextT(ctx, t)
117 l5d = createL5d()
118 emptyTrustAnchorSecret = buildSecret(l5d, []byte{}, []byte{})
119 trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
120 return ctx
121 }).
122 Test("return empty certificate if secret doesnt exist", func(ctx f2.Context, t *testing.T) f2.Context {
123 cert, err := SecretExists(ctx, k.Client)
124 assert.NoError(t, err)
125 assert.Equal(t, "", cert)
126 return ctx
127 }).
128 Test("error if certifcate key is empty for existing secret", func(ctx f2.Context, t *testing.T) f2.Context {
129 require.NoError(t, k.Client.Create(ctx, emptyTrustAnchorSecret))
130 cert, err := SecretExists(ctx, k.Client)
131 assert.Equal(t, "", cert)
132 assert.EqualError(t, err, fmt.Sprintf("secret was present, but %s was not present or empty", corev1.TLSCertKey))
133 return ctx
134 }).
135 Test("return certificate string if secret exists", func(ctx f2.Context, t *testing.T) f2.Context {
136 require.NoError(t, k.Client.Delete(ctx, emptyTrustAnchorSecret))
137 require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
138
139 cert, err := SecretExists(ctx, k.Client)
140 assert.Equal(t, "fake-cert", cert)
141 assert.NoError(t, err)
142 return ctx
143 }).
144 Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
145 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
146 return ctx
147 }).Feature()
148 f.Test(t, feature)
149 }
150
151 func TestCreateSecret(t *testing.T) {
152 var (
153 k *ktest.K8s
154 l5d *l5dv1alpha1.Linkerd
155 )
156 feature := f2.NewFeature("createSecret").
157 Setup("create linkerd CR", func(ctx f2.Context, t *testing.T) f2.Context {
158 k = ktest.FromContextT(ctx, t)
159 l5d = createL5d()
160 return ctx
161 }).
162 Test("create trust anchor seret if secret does not exist", func(ctx f2.Context, t *testing.T) f2.Context {
163 cert, err := createSecret(ctx, k.Client, l5d)
164 assert.NoError(t, err)
165 assert.NotEqual(t, "", cert)
166 assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
167 assert.Contains(t, cert, "-----END CERTIFICATE-----")
168 return ctx
169 }).
170 Test("return certificate if secret already exists", func(ctx f2.Context, t *testing.T) f2.Context {
171 cert, err := createSecret(ctx, k.Client, l5d)
172 assert.NoError(t, err)
173 assert.NotEqual(t, "", cert)
174 assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
175 assert.Contains(t, cert, "-----END CERTIFICATE-----")
176 return ctx
177 }).
178 Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
179 trustAnchorSecret := buildSecret(l5d, []byte{}, []byte{})
180 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
181 return ctx
182 }).Feature()
183 f.Test(t, feature)
184 }
185
186 func TestRotate(t *testing.T) {
187 var (
188 k *ktest.K8s
189 l5d *l5dv1alpha1.Linkerd
190 trustAnchorSecret *corev1.Secret
191 trustAnchorCert string
192 trustRootsCM *corev1.ConfigMap
193 )
194 feature := f2.NewFeature("rotate").
195 Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
196 k = ktest.FromContextT(ctx, t)
197 l5d = createL5d()
198 trustAnchorCert = "fake-cert"
199 trustAnchorSecret = buildSecret(l5d, []byte(trustAnchorCert), []byte{})
200
201
202 trustRootsCM = &corev1.ConfigMap{
203 ObjectMeta: metav1.ObjectMeta{
204 Name: linkerd.LinkerdIdentityConfigMap,
205 Namespace: linkerd.Namespace,
206 },
207 }
208 require.NoError(t, k.Client.Create(ctx, trustRootsCM))
209
210 return ctx
211 }).
212 Test("secret doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
213 require.Error(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
214 assert.NoError(t, Rotate(ctx, k.Client, l5d))
215 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
216 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
217 assert.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
218
219
220 require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.LinkerdIdentityConfigMap, Namespace: linkerd.Namespace}, trustRootsCM))
221 caBundle, exists := trustRootsCM.Data[CaBundleKey]
222 assert.True(t, exists)
223 assert.NotEqual(t, "", caBundle)
224 assert.Contains(t, caBundle, "-----BEGIN CERTIFICATE-----")
225 assert.Contains(t, caBundle, "-----END CERTIFICATE-----")
226
227 return ctx
228 }).
229 Test("secret already exists", func(ctx f2.Context, t *testing.T) f2.Context {
230 require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
231 assert.NoError(t, Rotate(ctx, k.Client, l5d))
232 assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
233 assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.DualAnchor)
234
235
236 require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.LinkerdIdentityConfigMap, Namespace: linkerd.Namespace}, trustRootsCM))
237 caBundle, exists := trustRootsCM.Data[CaBundleKey]
238 assert.True(t, exists)
239 assert.Contains(t, caBundle, "-----BEGIN CERTIFICATE-----")
240 assert.Contains(t, caBundle, "-----END CERTIFICATE-----")
241
242 return ctx
243 }).
244 Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
245 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
246 require.NoError(t, k.Client.Delete(ctx, trustRootsCM))
247 return ctx
248 }).
249 Feature()
250 f.Test(t, feature)
251 }
252
253 func TestIsRotated(t *testing.T) {
254 var (
255 k *ktest.K8s
256 l5d *l5dv1alpha1.Linkerd
257 trustAnchorSecret *corev1.Secret
258 )
259 feature := f2.NewFeature("IsRotated").
260 Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
261 k = ktest.FromContextT(ctx, t)
262 l5d = createL5d()
263 trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
264 return ctx
265 }).
266 Test("nil secret", func(ctx f2.Context, t *testing.T) f2.Context {
267
268 trustAnchorSecret, err := getSecret(ctx, k.Client)
269 assert.NoError(t, err)
270 assert.Nil(t, trustAnchorSecret)
271
272
273 assert.False(t, IsRotated(ctx, k.Client))
274
275 return ctx
276 }).
277 Test("annotation doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
278 require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
279
280 assert.Empty(t, trustAnchorSecret.Annotations[trustAnchorRotated])
281 assert.False(t, IsRotated(ctx, k.Client))
282 return ctx
283 }).
284 Test("annotation set", func(ctx f2.Context, t *testing.T) f2.Context {
285 secret := trustAnchorSecret.DeepCopy()
286 secret.Annotations = map[string]string{trustAnchorRotated: "true"}
287 require.NoError(t, k.Client.Patch(ctx, secret, client.StrategicMergeFrom(trustAnchorSecret.DeepCopy())))
288 assert.True(t, IsRotated(ctx, k.Client))
289 return ctx
290 }).
291 Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
292 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
293 return ctx
294 }).
295 Feature()
296 f.Test(t, feature)
297 }
298
299 func TestCaBundle(t *testing.T) {
300 var (
301 k *ktest.K8s
302 l5d *l5dv1alpha1.Linkerd
303 cmObj = types.NamespacedName{Namespace: linkerd.Namespace, Name: linkerd.LinkerdIdentityConfigMap}
304 identityCM = &corev1.ConfigMap{
305 ObjectMeta: metav1.ObjectMeta{
306 Name: linkerd.LinkerdIdentityConfigMap,
307 Namespace: linkerd.Namespace,
308 },
309 }
310 caSecret = "secret"
311 caExtraSecret = "extra-secret"
312 trustCert = "test-cert"
313 trustAnchorSecret = &corev1.Secret{}
314 )
315
316 featureUpdateCaBundle := f2.NewFeature("updateCaBundle").
317 Setup("create identity configmap", func(ctx f2.Context, t *testing.T) f2.Context {
318 k = ktest.FromContextT(ctx, t)
319 require.NoError(t, k.Client.Create(ctx, identityCM.DeepCopy()))
320 return ctx
321 }).
322 Test("no data", func(ctx f2.Context, t *testing.T) f2.Context {
323 newCaBundle, err := updateCaBundle(ctx, k.Client, "", "fake-secret")
324 require.NoError(t, err)
325
326 cm := &corev1.ConfigMap{}
327 require.NoError(t, k.Client.Get(ctx, cmObj, cm))
328
329 assert.NotEmpty(t, cm.Data[CaBundleKey])
330 assert.Equal(t, newCaBundle, cm.Data[CaBundleKey])
331 return ctx
332 }).
333 Test("ca bundle updated", func(ctx f2.Context, t *testing.T) f2.Context {
334 newCaBundle, err := updateCaBundle(ctx, k.Client, caSecret, caExtraSecret)
335 require.NoError(t, err)
336
337 cm := &corev1.ConfigMap{}
338 require.NoError(t, k.Client.Get(ctx, cmObj, cm))
339
340 assert.NotEmpty(t, cm.Data[CaBundleKey])
341 assert.Equal(t, newCaBundle, cm.Data[CaBundleKey])
342 return ctx
343 }).Feature()
344
345 featureGetCaBundle := f2.NewFeature("getCaBundle").
346 Setup("setup trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
347 l5d = createL5d()
348 trustAnchorSecret := buildSecret(l5d, []byte(trustCert), []byte{})
349 require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
350 return ctx
351 }).
352 Test("ca bundle exists", func(ctx f2.Context, t *testing.T) f2.Context {
353 caBundle, err := GetCaBundle(ctx, k.Client)
354 assert.NoError(t, err)
355 assert.Equal(t, fmt.Sprintf("%s%s", caSecret, caExtraSecret), caBundle)
356 return ctx
357 }).
358 Test("configmap doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
359 require.NoError(t, k.Client.Delete(ctx, identityCM))
360
361 caBundle, err := GetCaBundle(ctx, k.Client)
362 assert.NoError(t, err)
363 assert.True(t, errors.IsNotFound(k.Client.Get(ctx, cmObj, &corev1.ConfigMap{})))
364 assert.Equal(t, trustCert, caBundle)
365 return ctx
366 }).
367 Test("ca bundle doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
368 require.NoError(t, k.Client.Create(ctx, identityCM))
369 _, err := GetCaBundle(ctx, k.Client)
370 assert.ErrorContains(t, err, "ca bundle not found in configmap")
371 return ctx
372 }).
373 Feature()
374
375 featureCheckCaBundle := f2.NewFeature("checkCaBundle").
376 Setup("setup check", func(ctx f2.Context, t *testing.T) f2.Context {
377 require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
378 return ctx
379 }).
380 Test("secret does not exist", func(ctx f2.Context, t *testing.T) f2.Context {
381 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
382
383 caBundleOK, err := CheckCaBundle(ctx, k.Client)
384 assert.ErrorContains(t, err, "trust anchor secret does not exist")
385 assert.False(t, caBundleOK)
386 return ctx
387 }).
388 Test("root ca not in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
389 trustAnchorSecret = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: linkerd.TrustAnchorName, Namespace: linkerd.Namespace}}
390 require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
391
392 caBundleOK, err := CheckCaBundle(ctx, k.Client)
393 assert.ErrorContains(t, err, "secret was present, but tls.crt was not present or empty")
394 assert.False(t, caBundleOK)
395 return ctx
396 }).
397 Test("root ca in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
398 require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
399 cert, err := Create(ctx, k.Client, l5d)
400 require.NoError(t, err)
401 _, err = updateCaBundle(ctx, k.Client, "", cert)
402 require.NoError(t, err)
403
404 caBundleOK, err := CheckCaBundle(ctx, k.Client)
405 assert.NoError(t, err)
406 assert.True(t, caBundleOK)
407 return ctx
408 }).
409 Feature()
410
411 featureGetLastValidCa := f2.NewFeature("getLastValidCa").
412 Test("empty ca bundle", func(ctx f2.Context, t *testing.T) f2.Context {
413 lastCa, err := getLastValidCa("")
414 assert.NoError(t, err)
415 assert.Empty(t, lastCa)
416 return ctx
417 }).
418 Test("single cert in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
419 cert, _, err := generateSecret(ctx, l5d)
420 require.NoError(t, err)
421
422 lastCa, err := getLastValidCa(cert)
423 assert.NoError(t, err)
424 assert.Equal(t, cert, lastCa)
425 return ctx
426 }).
427 Test("multiple certs in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
428 certFirst, _, err := generateSecret(ctx, l5d)
429 require.NoError(t, err)
430
431 time.Sleep(time.Second)
432 certLast, _, err := generateSecret(ctx, l5d)
433 require.NoError(t, err)
434
435 lastCa, err := getLastValidCa(fmt.Sprintf("%s%s", certLast, certFirst))
436 assert.NoError(t, err)
437 assert.Equal(t, certLast, lastCa)
438 return ctx
439 }).
440 Feature()
441
442 f.Test(t,
443 featureUpdateCaBundle,
444 featureGetCaBundle,
445 featureCheckCaBundle,
446 featureGetLastValidCa,
447 )
448 }
449
450 func createL5d() *l5dv1alpha1.Linkerd {
451 return &l5dv1alpha1.Linkerd{
452 ObjectMeta: metav1.ObjectMeta{
453 Name: "linkerd",
454 Namespace: linkerd.Namespace,
455 UID: "12345",
456 },
457 }
458 }
459
View as plain text