1 package trustanchor
2
3 import (
4 "context"
5 "crypto/x509"
6 "fmt"
7 "slices"
8 "time"
9
10 "github.com/linkerd/linkerd2/pkg/tls"
11 corev1 "k8s.io/api/core/v1"
12 "k8s.io/apimachinery/pkg/api/errors"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/types"
15 "sigs.k8s.io/controller-runtime/pkg/client"
16
17 "edge-infra.dev/pkg/edge/linkerd"
18 l5dv1alpha1 "edge-infra.dev/pkg/edge/linkerd/k8s/apis/linkerd/v1alpha1"
19 "edge-infra.dev/pkg/edge/linkerd/k8s/controllers/metrics"
20 "edge-infra.dev/pkg/k8s/runtime/conditions"
21 "edge-infra.dev/pkg/lib/fog"
22 )
23
24 const (
25 CaBundleKey = "ca-bundle.crt"
26 ManualTrustAnchorRotation = "linkerd.io/manual-anchor-rotation"
27 trustAnchorRotated = "linkerd.io/trust-anchor-rotated"
28 )
29
30
31
32 func GenerateTrustAnchor(ctx context.Context) ([]byte, []byte, error) {
33 log := fog.FromContext(ctx).WithName("generate-trust-anchor")
34
35
36 rootCAkey, err := tls.GenerateKey()
37 if err != nil {
38 return nil, nil, fmt.Errorf("failed to generate key for root ca: %w", err)
39 }
40
41
42 certExpiryDate := time.Now().AddDate(linkerd.CertDurationYear, 0, 0)
43 metrics.RecordTrustAnchorExpiryTime(float64(certExpiryDate.Unix()))
44
45 tlsValidity := tls.Validity{
46 Lifetime: time.Until(certExpiryDate),
47 }
48 ca, err := tls.CreateRootCA("root.linkerd.cluster.local", rootCAkey, tlsValidity)
49 if err != nil {
50 return nil, nil, fmt.Errorf("failed to generate root ca: %w", err)
51 }
52
53 cert := ca.Cred.Crt.EncodePEM()
54 key, err := tls.EncodePrivateKeyPEM(rootCAkey)
55 if err != nil {
56 return nil, nil, fmt.Errorf("failed to encode private key: %w", err)
57 }
58 log.Info("generated a trust anchor certificate and key", "certificate expiry date", certExpiryDate)
59 return []byte(cert), key, nil
60 }
61
62 func buildSecret(l5d *l5dv1alpha1.Linkerd, cert, key []byte) *corev1.Secret {
63 return &corev1.Secret{
64 ObjectMeta: metav1.ObjectMeta{
65 Name: linkerd.TrustAnchorName,
66 Namespace: linkerd.Namespace,
67 OwnerReferences: linkerd.OwnerRef(l5d),
68 },
69 Type: corev1.SecretTypeTLS,
70 Data: map[string][]byte{
71 corev1.TLSCertKey: cert,
72 corev1.TLSPrivateKeyKey: key,
73 },
74 }
75 }
76
77
78
79
80
81
82
83
84 func CreateIfNotExists(ctx context.Context, c client.Client, l5d *l5dv1alpha1.Linkerd) (string, error) {
85 cert, err := SecretExists(ctx, c)
86 if err != nil {
87 conditions.MarkFalse(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.TrustAnchorSecretSetupFailedReason, "%v", err)
88 return "", err
89 }
90
91
92 if cert != "" {
93 return cert, nil
94 }
95
96 return Create(ctx, c, l5d)
97 }
98
99
100 func Create(ctx context.Context, c client.Client, l5d *l5dv1alpha1.Linkerd) (string, error) {
101 cert, err := createSecret(ctx, c, l5d)
102 if err != nil {
103 conditions.MarkFalse(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.TrustAnchorSecretSetupFailedReason, "%v", err)
104 return "", err
105 }
106
107 conditions.MarkTrue(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.SucceededReason, "Created trust anchor secret")
108 return cert, nil
109 }
110
111
112 func Rotate(ctx context.Context, c client.Client, l5d *l5dv1alpha1.Linkerd) error {
113 log := fog.FromContext(ctx).WithName("rotate")
114
115 newTrustAnchorSecret, newSecret, err := generateSecret(ctx, l5d)
116 if err != nil {
117 log.Error(err, "failed to generate trust anchor secret")
118 return err
119 }
120 if newSecret.Annotations == nil {
121 newSecret.Annotations = make(map[string]string)
122 }
123
124 newSecret.Annotations[trustAnchorRotated] = "true"
125
126 oldSecret, err := getSecret(ctx, c)
127 if err != nil {
128 return err
129 } else if oldSecret == nil {
130 log.Info("trust anchor secret doesn't exist - creating secret")
131 if err := c.Create(ctx, newSecret, linkerd.CreateOpts()); err != nil {
132 return err
133 }
134 }
135 oldTrustAnchorCert, err := getCert(oldSecret)
136 if err != nil {
137 log.Error(err, "failed to get old trust anchor certificate from secret")
138 return err
139 }
140
141 if _, err := updateCaBundle(ctx, c, oldTrustAnchorCert, newTrustAnchorSecret); err != nil {
142 log.Error(err, "failed updating the ca bundle in the identity configmap")
143 return err
144 }
145 log.Info("successfully updated the identity configmap with the new ca bundle")
146
147 secret, err := getSecret(ctx, c)
148 if err != nil {
149 return err
150 } else if secret == nil {
151 log.Info("trust anchor secret doesn't exist - creating secret")
152 conditions.MarkTrue(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.SucceededReason, "Created trust anchor secret")
153 return c.Create(ctx, newSecret, linkerd.CreateOpts())
154 }
155
156
157
158 if oldSecret != nil {
159 conditions.MarkTrue(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.DualAnchor, "successfully merged old and new secret")
160 return c.Patch(ctx, newSecret, client.StrategicMergeFrom(oldSecret.DeepCopy()))
161 }
162
163 conditions.MarkTrue(l5d, l5dv1alpha1.TrustAnchor, l5dv1alpha1.SucceededReason, "Created trust anchor secret")
164 return nil
165 }
166
167
168
169 func IsRotated(ctx context.Context, c client.Client) bool {
170 log := fog.FromContext(ctx).WithName("is-rotated")
171
172 secret, err := getSecret(ctx, c)
173 if err != nil {
174 log.Error(err, "failed to get secret")
175 return false
176 }
177 if secret == nil {
178 return false
179 }
180
181 _, annotationExists := secret.Annotations[trustAnchorRotated]
182 if !annotationExists {
183 log.Info("trust anchor rotation annotation does not exist - trust anchor has not been rotated")
184 return false
185 }
186
187 log.Info("the trust anchor has been rotated")
188 return true
189 }
190
191 func HasManualRotationAnnotation(l5d *l5dv1alpha1.Linkerd) bool {
192 _, exists := l5d.Annotations[ManualTrustAnchorRotation]
193 return exists
194 }
195
196 func RemoveRotationAnnotations(ctx context.Context, c client.Client, l5d *l5dv1alpha1.Linkerd) error {
197 secret, err := getSecret(ctx, c)
198 if err != nil {
199 return err
200 }
201 secretPatch := []byte(fmt.Sprintf(
202 `{"metadata":{"annotations":{"%s":null}}}`,
203 trustAnchorRotated),
204 )
205 if err := c.Patch(ctx, secret, client.RawPatch(types.MergePatchType, secretPatch)); err != nil {
206 return err
207 }
208
209 l5dPatch := []byte(fmt.Sprintf(
210 `{"metadata":{"annotations":{"%s":null}}}`,
211 ManualTrustAnchorRotation),
212 )
213 return c.Patch(ctx, l5d.DeepCopy(), client.RawPatch(types.MergePatchType, l5dPatch))
214 }
215
216
217
218 func SecretExists(ctx context.Context, c client.Client) (string, error) {
219 secret, err := getSecret(ctx, c)
220 if err != nil {
221 return "", err
222 } else if secret == nil {
223 return "", nil
224 }
225
226 return getCert(secret)
227 }
228
229 func getCert(secret *corev1.Secret) (string, error) {
230 if secret == nil {
231 return "", nil
232 }
233 data, ok := secret.Data[corev1.TLSCertKey]
234 if !ok || len(data) == 0 {
235 return "", fmt.Errorf("secret was present, but %s was not present or empty", corev1.TLSCertKey)
236 }
237 return string(secret.Data[corev1.TLSCertKey]), nil
238 }
239
240
241
242 func UpdateCaBundle(ctx context.Context, c client.Client) (string, error) {
243 log := fog.FromContext(ctx).WithName("update-ca-bundle")
244 caBundle, err := GetCaBundle(ctx, c)
245 if err != nil {
246 return "", err
247 }
248
249 if caBundleOK, err := CheckCaBundle(ctx, c); err != nil || !caBundleOK {
250 if err != nil {
251 return "", err
252 }
253 log.Info("ca bundle is out-of-date - updating...")
254
255 lastCa, err := getLastValidCa(caBundle)
256 if err != nil {
257 return "", err
258 }
259
260 newCa, err := SecretExists(ctx, c)
261 if err != nil {
262 return "", err
263 }
264 caBundle, err = updateCaBundle(ctx, c, lastCa, newCa)
265 if err != nil {
266 return "", err
267 }
268 }
269
270 return caBundle, nil
271 }
272
273
274 func updateCaBundle(ctx context.Context, c client.Client, oldTrustAnchorSecret, newTrustAnchorSecret string) (string, error) {
275 updatedCaBundle := fmt.Sprintf("%s%s", oldTrustAnchorSecret, newTrustAnchorSecret)
276
277 cm, err := getIdentityCM(ctx, c)
278 if err != nil {
279 return updatedCaBundle, err
280 }
281 if cm == nil {
282 return updatedCaBundle, fmt.Errorf("unable to update the ca bundle in the identity configmap")
283 }
284
285 cmPatch := cm.DeepCopy()
286 if cmPatch.Data == nil {
287 cmPatch.Data = make(map[string]string)
288 }
289 cmPatch.Data[CaBundleKey] = updatedCaBundle
290
291 return updatedCaBundle, c.Patch(ctx, cmPatch, client.StrategicMergeFrom(cm.DeepCopy()))
292 }
293
294
295
296 func GetCaBundle(ctx context.Context, c client.Client) (string, error) {
297 log := fog.FromContext(ctx).WithName("get-ca-bundle")
298 const getCaFailMsg string = "unable to get the ca bundle"
299
300 cm, err := getIdentityCM(ctx, c)
301 if errors.IsNotFound(err) {
302 log.Info("identity configmap not found - get ca from the trust anchor secret")
303 caBundle, err := SecretExists(ctx, c)
304
305 if err == nil && caBundle == "" {
306 missingTrustErr := fmt.Errorf("trustanchor secret does not exist")
307 log.Error(missingTrustErr, getCaFailMsg)
308 return "", missingTrustErr
309 } else if err != nil {
310 log.Error(err, getCaFailMsg)
311 return "", err
312 }
313
314 return caBundle, nil
315 } else if err != nil {
316 log.Error(err, getCaFailMsg)
317 return "", err
318 }
319
320 bundle, exists := cm.Data[CaBundleKey]
321 if !exists {
322 missingCABundleErr := fmt.Errorf("ca bundle not found in configmap")
323 log.Error(missingCABundleErr, getCaFailMsg)
324 return "", missingCABundleErr
325 }
326 return bundle, nil
327 }
328
329
330
331 func CheckCaBundle(ctx context.Context, c client.Client) (bool, error) {
332 log := fog.FromContext(ctx).WithName("check-ca-bundle")
333 currentRootCa, err := SecretExists(ctx, c)
334 if err != nil {
335 return false, err
336 } else if currentRootCa == "" {
337 return false, fmt.Errorf("trust anchor secret does not exist")
338 }
339
340 caBundle, err := GetCaBundle(ctx, c)
341 if err != nil {
342 return false, err
343 }
344
345 bundleCerts, err := tls.DecodePEMCertificates(caBundle)
346 if err != nil {
347 return false, err
348 }
349 currentCert, err := tls.DecodePEMCertificates(currentRootCa)
350 if err != nil {
351 return false, err
352 }
353 if slices.ContainsFunc(currentCert, func(entry *x509.Certificate) bool {
354 for _, cert := range bundleCerts {
355 if cert.Equal(entry) {
356 return true
357 }
358 }
359 return false
360 }) {
361 return true, nil
362 }
363
364 log.Info("identity configmap is not up-to-date")
365 return false, nil
366 }
367
368
369 func getLastValidCa(caBundle string) (string, error) {
370 certs, err := tls.DecodePEMCertificates(caBundle)
371 if err != nil {
372 return "", err
373 }
374
375 if len(certs) == 0 {
376 return "", nil
377 } else if len(certs) > 1 {
378
379
380 slices.SortFunc(certs, func(certA, certB *x509.Certificate) int {
381 if certA.NotAfter.Before(certB.NotAfter) {
382 return -1
383 }
384 return 1
385 })
386 }
387
388 return tls.EncodeCertificatesPEM(certs[len(certs)-1]), nil
389 }
390
391 func getIdentityCM(ctx context.Context, c client.Client) (*corev1.ConfigMap, error) {
392 cm := &corev1.ConfigMap{}
393 err := c.Get(ctx, types.NamespacedName{Name: linkerd.LinkerdIdentityConfigMap, Namespace: linkerd.Namespace}, cm)
394 if err != nil {
395 return nil, err
396 }
397 return cm, nil
398 }
399
400 func getSecret(ctx context.Context, c client.Client) (*corev1.Secret, error) {
401 log := fog.FromContext(ctx).WithName("get-secret")
402 secret := &corev1.Secret{}
403 err := c.Get(ctx, linkerd.TrustAnchorKey(), secret)
404 if errors.IsNotFound(err) {
405 log.Info("the trust anchor secret was not found")
406 return nil, nil
407 } else if err != nil {
408 log.Error(err, "failed to get trust anchor secret")
409 return nil, fmt.Errorf("failed to check if trust anchor secret exists: %w", err)
410 }
411 return secret, err
412 }
413
414
415
416
417 func createSecret(ctx context.Context, c client.Client, l5d *l5dv1alpha1.Linkerd) (string, error) {
418 log := fog.FromContext(ctx).WithName("create-secret")
419 cert, secret, err := generateSecret(ctx, l5d)
420 if err != nil {
421 log.Error(err, "failed to generate trust anchor secret")
422 return "", err
423 }
424
425 if err := c.Create(ctx, secret, linkerd.CreateOpts()); client.IgnoreAlreadyExists(err) != nil {
426 log.Error(err, "failed to create trust anchor secret")
427 return "", fmt.Errorf("failed to create secret")
428 }
429 log.Info("the trust anchor secret was created or already exists")
430 return cert, nil
431 }
432
433 func generateSecret(ctx context.Context, l5d *l5dv1alpha1.Linkerd) (string, *corev1.Secret, error) {
434 cert, key, err := GenerateTrustAnchor(ctx)
435 if err != nil {
436 return "", nil, err
437 }
438 return string(cert), buildSecret(l5d, cert, key), nil
439 }
440
View as plain text