1 package bannerctl
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strconv"
8 "strings"
9 "time"
10
11 kms "cloud.google.com/go/kms/apiv1"
12 "cloud.google.com/go/kms/apiv1/kmspb"
13 "github.com/go-logr/logr"
14 "github.com/google/uuid"
15 "google.golang.org/protobuf/types/known/fieldmaskpb"
16 ctrl "sigs.k8s.io/controller-runtime"
17 "sigs.k8s.io/controller-runtime/pkg/client"
18
19 "edge-infra.dev/pkg/edge/api/services/channels"
20 bannerAPI "edge-infra.dev/pkg/edge/apis/banner/v1alpha1"
21 "edge-infra.dev/pkg/edge/edgeencrypt"
22 "edge-infra.dev/pkg/k8s/runtime/controller/reconcile"
23 "edge-infra.dev/pkg/k8s/runtime/controller/reconcile/recerr"
24 )
25
26 type EncryptionKeyManagementReconciler struct {
27 client.Client
28 Log logr.Logger
29 Conditions reconcile.Conditions
30 ForemanProjectID string
31 GCPRegion string
32 SecretManager secretManager
33 KmsClient *kms.KeyManagementClient
34 kmsKey edgeencrypt.KmsKey
35 ChannelService channels.Service
36
37 IntervalTime time.Duration
38 RequeueTime time.Duration
39 ResourceTimeout time.Duration
40 }
41
42 func (r *EncryptionKeyManagementReconciler) SetupWithManager(mgr ctrl.Manager) error {
43 r.kmsKey = edgeencrypt.KmsKey{ProjectID: r.ForemanProjectID, Location: r.GCPRegion}
44 return ctrl.NewControllerManagedBy(mgr).
45 For(&bannerAPI.Banner{}).
46 Complete(r)
47 }
48
49 func (r *EncryptionKeyManagementReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) {
50 banner := &bannerAPI.Banner{}
51 if err := r.Get(ctx, req.NamespacedName, banner); err != nil {
52 return ctrl.Result{}, client.IgnoreNotFound(err)
53 }
54 log := ctrl.Log.WithValues("banner", banner.Name)
55
56
57 bcs, err := getBannerChannels(ctx, r.ChannelService, banner.Name)
58 if err != nil {
59 return ctrl.Result{RequeueAfter: r.IntervalTime}, nil
60 }
61 if len(bcs) == 0 {
62 return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
63 }
64
65 recErr = r.reconcile(ctx, banner, bcs)
66 if recErr != nil {
67 log.Error(recErr, "failed to reconcile")
68 return ctrl.Result{RequeueAfter: r.IntervalTime}, nil
69 }
70
71 return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
72 }
73
74 func (r *EncryptionKeyManagementReconciler) reconcile(ctx context.Context, banner *bannerAPI.Banner, chs []channels.BannerChannel) recerr.Error {
75 secretClient, err := r.SecretManager.NewWithOptions(ctx, banner.Spec.GCP.ProjectID)
76 if err != nil {
77 return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
78 }
79
80
81 bannerEdgeID, err := uuid.Parse(banner.Name)
82 if err != nil {
83 return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
84 }
85
86
87
88 for _, channel := range chs {
89
90 if channel.ShouldBumpKeyVersion() {
91 ver, err := r.createNewKeyVersion(ctx, banner, fmt.Sprintf(edgeencrypt.EncryptionSecret, channel.ID))
92 if err != nil {
93 return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
94 }
95 err = r.rotateKey(ctx, secretClient, bannerEdgeID, banner, channel, ver)
96 if err != nil {
97 return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
98 }
99 }
100 if _, validKey := channel.LatestKeyVersion(); !validKey {
101 continue
102 }
103 for _, k := range channel.KeyVersions {
104 if k.IsExpired() {
105 key := r.kmsKey.KeyPath(banner.Name, fmt.Sprintf(edgeencrypt.EncryptionSecret, channel.ID), fmt.Sprintf("%d", k.Version))
106 req := &kmspb.UpdateCryptoKeyVersionRequest{
107 CryptoKeyVersion: &kmspb.CryptoKeyVersion{
108 Name: key,
109 State: kmspb.CryptoKeyVersion_DISABLED,
110 },
111 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
112 }
113 if _, err = r.KmsClient.UpdateCryptoKeyVersion(ctx, req); err != nil {
114 return recerr.New(fmt.Errorf("fail to disable key: %s, %w", key, err),
115 bannerAPI.PlatformSecretsCreationFailedReason)
116 }
117 r.Log.Info("encryption key disabled", "key", key)
118 _, err = r.ChannelService.DeleteChannelKeyVersion(ctx, k.ID)
119 if err != nil {
120 return recerr.New(fmt.Errorf("fail to delete channel key version: %s, %w", k.ID, err),
121 bannerAPI.PlatformSecretsCreationFailedReason)
122 }
123 }
124 }
125 }
126 return nil
127 }
128
129 func (r *EncryptionKeyManagementReconciler) createNewKeyVersion(ctx context.Context, banner *bannerAPI.Banner, channel string) (int, error) {
130 req := &kmspb.CreateCryptoKeyVersionRequest{Parent: r.kmsKey.Key(banner.Name, channel)}
131 result, err := r.KmsClient.CreateCryptoKeyVersion(ctx, req)
132 if err != nil {
133 return 0, fmt.Errorf("failed to create key version: %w", err)
134 }
135
136 token := strings.Split(result.Name, "/")
137 version := token[len(token)-1]
138
139 versionInt, err := strconv.Atoi(version)
140 if err != nil {
141 return 0, fmt.Errorf("failed to convert version to int: %w", err)
142 }
143 return versionInt, nil
144 }
145
146 func (r *EncryptionKeyManagementReconciler) rotateKey(ctx context.Context, secretClient secretManagerClient, bannerEdgeID uuid.UUID, banner *bannerAPI.Banner, channel channels.BannerChannel, version int) error {
147 ckv := channels.ChannelKeyVersion{
148 ChannelID: channel.ID,
149 BannerEdgeID: bannerEdgeID,
150 Version: version,
151 SecretManagerLink: secretManagerLink(banner.Spec.GCP.ProjectID, fmt.Sprintf(edgeencrypt.EncryptionSecretManager, channel.ID)),
152 }
153 _, err := r.ChannelService.CreateChannelKeyVersion(ctx, ckv)
154 if err != nil {
155 return fmt.Errorf("failed to create channel key version: %w", err)
156 }
157
158 key := r.kmsKey.KeyPath(banner.Name, fmt.Sprintf(edgeencrypt.EncryptionSecret, channel.ID), fmt.Sprintf("%d", version))
159
160 pubKey, err := getPublicKeyWithRetry(ctx, r.KmsClient, key, r.ResourceTimeout)
161 if err != nil {
162 return fmt.Errorf("failed to get public key from kms to rotate: %w", err)
163 }
164
165 pk, err := edgeencrypt.NewPublicKey(pubKey.Pem, fmt.Sprintf("%d", version))
166 if err != nil {
167 return fmt.Errorf("failed to create public key: %w", err)
168 }
169 data, err := json.Marshal(pk)
170 if err != nil {
171 return fmt.Errorf("failed to marshal public key: %w", err)
172 }
173
174 err = saveToSecretManager(ctx, secretClient, fmt.Sprintf(edgeencrypt.EncryptionSecretManager, channel.ID), data, version, map[string]string{"channel": channel.Name})
175 if err != nil {
176 return fmt.Errorf("failed to save rotated public key to secret manager: %w", err)
177 }
178 return nil
179 }
180
View as plain text