1 package linkerd
2
3 import (
4 "fmt"
5 "testing"
6 "time"
7
8 "cuelang.org/go/pkg/strings"
9 goVersion "github.com/hashicorp/go-version"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 "gotest.tools/v3/assert/cmp"
13 "gotest.tools/v3/poll"
14 corev1 "k8s.io/api/core/v1"
15 "k8s.io/apimachinery/pkg/types"
16 "sigs.k8s.io/controller-runtime/pkg/client"
17
18 "edge-infra.dev/pkg/edge/linkerd"
19 "edge-infra.dev/pkg/edge/linkerd/certs/trustanchor"
20 l5dv1alpha1 "edge-infra.dev/pkg/edge/linkerd/k8s/apis/linkerd/v1alpha1"
21 "edge-infra.dev/test/f2"
22 "edge-infra.dev/test/f2/x/ktest"
23 "edge-infra.dev/test/f2/x/ktest/kustomization"
24 l5dhelm "edge-infra.dev/third_party/k8s/linkerd/helm"
25 )
26
27 func TestManualTrustAnchorRotation(t *testing.T) {
28 var (
29 k *ktest.K8s
30 l5d *l5dv1alpha1.Linkerd
31 initialTrustAnchorSecret *corev1.Secret
32 injected []string
33 )
34 manualRotation := f2.NewFeature("manual trust anchor rotation").
35 Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
36 k = ktest.FromContextT(ctx, t)
37
38 nodes := &corev1.NodeList{}
39 require.NoError(t, k.Client.List(ctx, nodes))
40
41 initialTrustAnchorSecret = &corev1.Secret{}
42 require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), initialTrustAnchorSecret))
43 return ctx
44 }).
45 Test("manual rotation", func(ctx f2.Context, t *testing.T) f2.Context {
46 l5d = &l5dv1alpha1.Linkerd{}
47 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: "linkerd.edge.ncr.com"}, l5d))
48 patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(
49 `{"metadata":{"annotations":{"%s":"%s"}}}`,
50 trustanchor.ManualTrustAnchorRotation, "true"),
51 ))
52 assert.NoError(t, k.Client.Patch(ctx, l5d, patch))
53 assert.Eventually(t, func() bool { return trustanchor.IsRotated(ctx, k.Client) }, ktest.Timeout*2, ktest.Tick, "Wait for linkerd trust anchor secret to be rotated")
54 return ctx
55 }).
56 Test("check manual rotation annotation", func(ctx f2.Context, t *testing.T) f2.Context {
57 l5d = &l5dv1alpha1.Linkerd{}
58 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: "linkerd.edge.ncr.com"}, l5d))
59 assert.Eventually(t, func() bool { return !trustanchor.HasManualRotationAnnotation(l5d) }, ktest.Timeout*2, ktest.Tick, "wait for manual rotation annotation to be removed")
60 return ctx
61 }).
62 Test("trust anchor check", func(ctx f2.Context, t *testing.T) f2.Context {
63 currentTrustAnchorSecret := &corev1.Secret{}
64 assert.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), currentTrustAnchorSecret))
65 assert.NotEqual(t, currentTrustAnchorSecret.Data["tls.crt"], initialTrustAnchorSecret.Data["tls.crt"])
66 caBundleOK, err := trustanchor.CheckCaBundle(ctx, k.Client)
67 assert.NoError(t, err)
68 assert.True(t, caBundleOK)
69 return ctx
70 }).
71 Test("verify linkerd CR status", func(ctx f2.Context, t *testing.T) f2.Context {
72 l5d := &l5dv1alpha1.Linkerd{}
73 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: "linkerd.edge.ncr.com"}, l5d))
74 assert.True(t, isReady(l5d.Status.Conditions))
75 assert.NotNil(t, l5d.Status.Version)
76 injected = l5d.Status.InjectedNamespaces
77 return ctx
78 }).
79 Test("verify linkerdworkloadinjection CR status", func(ctx f2.Context, t *testing.T) f2.Context {
80 workloadinjection := &l5dv1alpha1.LinkerdWorkloadInjection{}
81 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.Namespace, Namespace: linkerd.Namespace}, workloadinjection))
82 assert.True(t, isReady(workloadinjection.Status.Conditions))
83 return ctx
84 }).
85 Test("verify injected workloads are meshed successfully", func(ctx f2.Context, t *testing.T) f2.Context {
86 podList := &corev1.PodList{}
87 require.NoError(t, k.Client.List(ctx, podList))
88 for _, pod := range podList.Items {
89 if pod.Status.Phase == corev1.PodRunning && isInjected(injected, pod.Namespace) && !pod.Spec.HostNetwork && !isLinkerdDisabled(pod) {
90 assert.True(t, isMeshed(pod) && proxyReady(pod), pod.Name)
91 }
92 }
93 return ctx
94 }).
95 Feature()
96 f.Test(t, manualRotation)
97 }
98
99
100
101 func TestAutomaticTrustAnchorRotation(t *testing.T) {
102 const (
103 l5dObjName = l5dv1alpha1.Name
104 )
105 var (
106 k *ktest.K8s
107 l5d *l5dv1alpha1.Linkerd
108 initialTrustAnchorSecret *corev1.Secret
109 injected []string
110 latestL5dVersion = l5dhelm.Version
111 )
112 automaticRotation := f2.NewFeature("automatic trust anchor rotation").
113 Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
114 k = ktest.FromContextT(ctx, t)
115
116 nodes := &corev1.NodeList{}
117 require.NoError(t, k.Client.List(ctx, nodes))
118
119 initialTrustAnchorSecret = &corev1.Secret{}
120 require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), initialTrustAnchorSecret))
121 return ctx
122 }).
123 Setup("linkerd version check", func(ctx f2.Context, t *testing.T) f2.Context {
124 l5d = &l5dv1alpha1.Linkerd{}
125 require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: l5dObjName}, l5d))
126
127 versionCheckSuccess, err := isLinkerdUpgradable(l5d, latestL5dVersion)
128 require.NoError(t, err)
129 if !versionCheckSuccess {
130 t.SkipNow()
131 }
132 return ctx
133 }).
134 Setup("linkerd upgrade", func(ctx f2.Context, t *testing.T) f2.Context {
135 crdManifests, err := kustomization.ProcessManifests(ctx.RunID, linkerdCRDManifests, linkerd.Namespace)
136 require.NoError(t, err)
137 for _, manifest := range crdManifests {
138 require.NoError(t, k.Client.Create(ctx, manifest))
139 }
140 controllerManifests, err := kustomization.ProcessManifests(ctx.RunID, linkerdControllerManifests, linkerd.Namespace)
141 require.NoError(t, err)
142 for _, manifest := range controllerManifests {
143 require.NoError(t, k.Client.Create(ctx, manifest))
144 }
145 return ctx
146 }).
147 Setup("wait for linkerd upgrade", func(ctx f2.Context, t *testing.T) f2.Context {
148 l5d = &l5dv1alpha1.Linkerd{}
149 require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: l5dObjName}, l5d))
150
151 k.WaitOn(t, k.Check(l5d, func(_ client.Object) cmp.Result {
152 require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: l5dObjName}, l5d))
153 if l5d.Status.Version == latestL5dVersion {
154 return cmp.ResultSuccess
155 }
156 return cmp.ResultFailure("linkerd not upgraded")
157 }), poll.WithTimeout(time.Minute*2))
158 return ctx
159 }).
160 Test("verify linkerd CR status", func(ctx f2.Context, t *testing.T) f2.Context {
161 l5d := &l5dv1alpha1.Linkerd{}
162 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: "linkerd.edge.ncr.com"}, l5d))
163 assert.True(t, isReady(l5d.Status.Conditions))
164 assert.NotNil(t, l5d.Status.Version)
165 injected = l5d.Status.InjectedNamespaces
166 return ctx
167 }).
168 Test("verify linkerdworkloadinjection CR status", func(ctx f2.Context, t *testing.T) f2.Context {
169 workloadinjection := &l5dv1alpha1.LinkerdWorkloadInjection{}
170 assert.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.Namespace, Namespace: linkerd.Namespace}, workloadinjection))
171 assert.True(t, isReady(workloadinjection.Status.Conditions))
172 return ctx
173 }).
174 Test("verify injected workloads are meshed successfully", func(ctx f2.Context, t *testing.T) f2.Context {
175 podList := &corev1.PodList{}
176 require.NoError(t, k.Client.List(ctx, podList))
177 for _, pod := range podList.Items {
178 if pod.Status.Phase == corev1.PodRunning && isInjected(injected, pod.Namespace) && !pod.Spec.HostNetwork && !isLinkerdDisabled(pod) {
179 assert.True(t, isMeshed(pod) && proxyReady(pod), pod.Name)
180 }
181 }
182 return ctx
183 }).Feature()
184 f.Test(t, automaticRotation)
185 }
186
187 func isLinkerdUpgradable(l5d *l5dv1alpha1.Linkerd, latestL5dVersion string) (bool, error) {
188 currentVersion, err := goVersion.NewVersion(strings.TrimPrefix(l5d.Status.Version, "edge-"))
189 if err != nil {
190 return false, err
191 }
192 latestVersion, err := goVersion.NewVersion(strings.TrimPrefix(latestL5dVersion, "edge-"))
193 if err != nil {
194 return false, err
195 }
196 return currentVersion.LessThan(latestVersion), nil
197 }
198
View as plain text