Skip to content

Commit dfe4f7c

Browse files
committed
pkg/operator/encryption: Make key_controller KMS config aware
Signed-off-by: Swarup Ghosh <swghosh@redhat.com>
1 parent 2b3a9ab commit dfe4f7c

File tree

3 files changed

+119
-38
lines changed

3 files changed

+119
-38
lines changed

pkg/operator/encryption/controllers/key_controller.go

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import (
2020
"k8s.io/klog/v2"
2121
"k8s.io/utils/ptr"
2222

23+
configv1 "github.com/openshift/api/config/v1"
2324
operatorv1 "github.com/openshift/api/operator/v1"
2425
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
2526
configv1informers "github.com/openshift/client-go/config/informers/externalversions/config/v1"
2627
applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1"
2728

2829
"github.com/openshift/library-go/pkg/controller/factory"
2930
"github.com/openshift/library-go/pkg/operator/encryption/crypto"
31+
"github.com/openshift/library-go/pkg/operator/encryption/encryptionconfig"
3032
"github.com/openshift/library-go/pkg/operator/encryption/secrets"
3133
"github.com/openshift/library-go/pkg/operator/encryption/state"
3234
"github.com/openshift/library-go/pkg/operator/encryption/statemachine"
@@ -159,7 +161,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) (
159161
}
160162

161163
func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) error {
162-
currentMode, externalReason, err := c.getCurrentModeAndExternalReason(ctx)
164+
currentKeyState, err := c.getCurrentEncryptionModeWithExternalReason(ctx)
163165
if err != nil {
164166
return err
165167
}
@@ -175,13 +177,15 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact
175177

176178
// avoid intended start of encryption
177179
hasBeenOnBefore := currentConfig != nil || len(secrets) > 0
178-
if currentMode == state.Identity && !hasBeenOnBefore {
180+
if currentKeyState.Mode == state.Identity && !hasBeenOnBefore {
179181
return nil
180182
}
181183

182184
var (
183185
newKeyRequired bool
184186
newKeyID uint64
187+
latestKeyID uint64
188+
ok bool
185189
reasons []string
186190
)
187191

@@ -191,23 +195,34 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact
191195

192196
var commonReason *string
193197
for gr, grKeys := range desiredEncryptionState {
194-
latestKeyID, internalReason, needed := needsNewKey(grKeys, currentMode, externalReason, encryptedGRs)
198+
// if kmsKeyID in GR ReadKey is not the same as current kmsKeyID, needed is true.
199+
ks, needed := needsNewKeyWithInternalReason(grKeys, currentKeyState.Mode, currentKeyState.KMSKeyID, currentKeyState.ExternalReason, encryptedGRs)
195200
if !needed {
196201
continue
197202
}
198203

204+
if ks.Mode != state.KMS {
205+
latestKeyID, ok = state.NameToKeyID(ks.Key.Name)
206+
if !ok {
207+
latestKeyID = 0
208+
}
209+
}
210+
199211
if commonReason == nil {
200-
commonReason = &internalReason
201-
} else if *commonReason != internalReason {
212+
commonReason = &ks.InternalReason
213+
} else if *commonReason != ks.InternalReason {
202214
commonReason = ptr.To("") // this means we have no common reason
203215
}
204216

205217
newKeyRequired = true
218+
219+
// tracking the newKeyID is only required for non-KMS
206220
nextKeyID := latestKeyID + 1
207-
if newKeyID < nextKeyID {
221+
if ks.Mode != state.KMS && newKeyID < nextKeyID {
208222
newKeyID = nextKeyID
209223
}
210-
reasons = append(reasons, fmt.Sprintf("%s-%s", gr.Resource, internalReason))
224+
225+
reasons = append(reasons, fmt.Sprintf("%s-%s", gr.Resource, ks.InternalReason))
211226
}
212227
if !newKeyRequired {
213228
return nil
@@ -216,12 +231,20 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact
216231
reasons = []string{*commonReason} // don't repeat reasons
217232
}
218233

219-
sort.Sort(sort.StringSlice(reasons))
220-
internalReason := strings.Join(reasons, ", ")
221-
keySecret, err := c.generateKeySecret(newKeyID, currentMode, internalReason, externalReason)
234+
sort.Strings(reasons)
235+
currentKeyState.InternalReason = strings.Join(reasons, ", ")
236+
237+
var keySecret *corev1.Secret
238+
if currentKeyState.Mode == state.KMS {
239+
keySecret, err = c.generateKMSKeySecret(currentKeyState.KMSConfig, currentKeyState.InternalReason, currentKeyState.ExternalReason)
240+
} else {
241+
keySecret, err = c.generateLocalKeySecret(newKeyID, currentKeyState.Mode, currentKeyState.InternalReason, currentKeyState.ExternalReason)
242+
}
243+
222244
if err != nil {
223245
return fmt.Errorf("failed to create key: %v", err)
224246
}
247+
225248
_, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{})
226249
if errors.IsAlreadyExists(createErr) {
227250
return c.validateExistingSecret(ctx, keySecret, newKeyID)
@@ -242,20 +265,31 @@ func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *c
242265
return err
243266
}
244267

268+
ks, err := secrets.ToKeyState(actualKeySecret)
269+
if err != nil {
270+
return fmt.Errorf("secret %s/%s is invalid, new keys cannot be created for encryption target", keySecret.Namespace, keySecret.Name)
271+
}
272+
273+
if ks.Mode == state.KMS && ks.KMSKeyID != "" {
274+
return nil
275+
}
276+
277+
if ks.Mode == state.KMS && ks.KMSKeyID == "" {
278+
// kmsKeyID is mandatory in case of KMS
279+
return fmt.Errorf("secret %s/%s is invalid, new KMS keys cannot be created for encryption target", keySecret.Namespace, keySecret.Name)
280+
}
281+
282+
// checks for local aes (non-KMS) keys only
245283
actualKeyID, ok := state.NameToKeyID(actualKeySecret.Name)
246284
if !ok || actualKeyID != keyID {
247285
// TODO we can just get stuck in degraded here ...
248-
return fmt.Errorf("secret %s has an invalid name, new keys cannot be created for encryption target", keySecret.Name)
249-
}
250-
251-
if _, err := secrets.ToKeyState(actualKeySecret); err != nil {
252-
return fmt.Errorf("secret %s is invalid, new keys cannot be created for encryption target", keySecret.Name)
286+
return fmt.Errorf("secret %s/%s has an invalid name, new keys cannot be created for encryption target", keySecret.Namespace, keySecret.Name)
253287
}
254288

255289
return nil // we made this key earlier
256290
}
257291

258-
func (c *keyController) generateKeySecret(keyID uint64, currentMode state.Mode, internalReason, externalReason string) (*corev1.Secret, error) {
292+
func (c *keyController) generateLocalKeySecret(keyID uint64, currentMode state.Mode, internalReason, externalReason string) (*corev1.Secret, error) {
259293
bs := crypto.ModeToNewKeyFunc[currentMode]()
260294
ks := state.KeyState{
261295
Key: apiserverv1.Key{
@@ -269,50 +303,85 @@ func (c *keyController) generateKeySecret(keyID uint64, currentMode state.Mode,
269303
return secrets.FromKeyState(c.instanceName, ks)
270304
}
271305

272-
func (c *keyController) getCurrentModeAndExternalReason(ctx context.Context) (state.Mode, string, error) {
306+
func (c *keyController) generateKMSKeySecret(kmsConfig *configv1.KMSConfig, internalReason, externalReason string) (*corev1.Secret, error) {
307+
kmsConfig = kmsConfig.DeepCopy()
308+
309+
kmsKeyID, err := encryptionconfig.HashKMSConfig(*kmsConfig)
310+
if err != nil {
311+
return nil, err
312+
}
313+
314+
ks := state.KeyState{
315+
Mode: state.KMS,
316+
InternalReason: internalReason,
317+
ExternalReason: externalReason,
318+
KMSKeyID: kmsKeyID,
319+
KMSConfig: kmsConfig,
320+
}
321+
return secrets.FromKeyState(c.instanceName, ks)
322+
}
323+
324+
func (c *keyController) getCurrentEncryptionModeWithExternalReason(ctx context.Context) (state.KeyState, error) {
273325
apiServer, err := c.apiServerClient.Get(ctx, "cluster", metav1.GetOptions{})
274326
if err != nil {
275-
return "", "", err
327+
return state.KeyState{}, err
276328
}
277329

278330
operatorSpec, _, _, err := c.operatorClient.GetOperatorState()
279331
if err != nil {
280-
return "", "", err
332+
return state.KeyState{}, err
281333
}
282334

283335
encryptionConfig, err := structuredUnsupportedConfigFrom(operatorSpec.UnsupportedConfigOverrides.Raw, c.unsupportedConfigPrefix)
284336
if err != nil {
285-
return "", "", err
337+
return state.KeyState{}, err
286338
}
287339

288340
reason := encryptionConfig.Encryption.Reason
289341
switch currentMode := state.Mode(apiServer.Spec.Encryption.Type); currentMode {
290342
case state.AESCBC, state.AESGCM, state.Identity: // secretbox is disabled for now
291-
return currentMode, reason, nil
343+
return state.KeyState{Mode: currentMode, ExternalReason: reason}, nil
344+
case state.KMS:
345+
kmsConfig := apiServer.Spec.Encryption.KMS.DeepCopy()
346+
347+
kmsKeyID, err := encryptionconfig.HashKMSConfig(*kmsConfig)
348+
if err != nil {
349+
return state.KeyState{}, fmt.Errorf("encryption mode configured: %s, but provided kms config could not generate required kms key id %v", currentMode, err)
350+
}
351+
352+
ks := state.KeyState{
353+
Mode: state.KMS,
354+
KMSKeyID: kmsKeyID,
355+
KMSConfig: kmsConfig,
356+
ExternalReason: reason,
357+
}
358+
return ks, nil
292359
case "": // unspecified means use the default (which can change over time)
293-
return state.DefaultMode, reason, nil
360+
return state.KeyState{Mode: state.DefaultMode, ExternalReason: reason}, nil
294361
default:
295-
return "", "", fmt.Errorf("unknown encryption mode configured: %s", currentMode)
362+
return state.KeyState{}, fmt.Errorf("unknown encryption mode configured: %s", currentMode)
296363
}
297364
}
298365

299-
// needsNewKey checks whether a new key must be created for the given resource. If true, it also returns the latest
366+
// needsNewKeyWithInternalReason checks whether a new key must be created for the given resource. If true, it also returns the latest
300367
// used key ID and a reason string.
301-
func needsNewKey(grKeys state.GroupResourceState, currentMode state.Mode, externalReason string, encryptedGRs []schema.GroupResource) (uint64, string, bool) {
368+
func needsNewKeyWithInternalReason(grKeys state.GroupResourceState, currentMode state.Mode, optionalCurrentKMSKeyID string, externalReason string, encryptedGRs []schema.GroupResource) (state.KeyState, bool) {
302369
// we always need to have some encryption keys unless we are turned off
303370
if len(grKeys.ReadKeys) == 0 {
304-
return 0, "key-does-not-exist", currentMode != state.Identity
371+
return state.KeyState{InternalReason: "key-does-not-exist"}, currentMode != state.Identity
305372
}
306373

307374
latestKey := grKeys.ReadKeys[0]
308375
latestKeyID, ok := state.NameToKeyID(latestKey.Key.Name)
309376
if !ok {
310-
return latestKeyID, fmt.Sprintf("key-secret-%d-is-invalid", latestKeyID), true
377+
latestKey.InternalReason = fmt.Sprintf("key-secret-%d-is-invalid", latestKeyID)
378+
return latestKey, true
311379
}
312380

313381
// if latest secret has been deleted, we will never be able to migrate to that key.
314382
if !latestKey.Backed {
315-
return latestKeyID, fmt.Sprintf("encryption-config-key-%d-not-backed-by-secret", latestKeyID), true
383+
latestKey.InternalReason = fmt.Sprintf("encryption-config-key-%d-not-backed-by-secret", latestKeyID)
384+
return latestKey, true
316385
}
317386

318387
// check that we have pruned read-keys: the write-keys, plus at most one more backed read-key (potentially some unbacked once before)
@@ -323,32 +392,41 @@ func needsNewKey(grKeys state.GroupResourceState, currentMode state.Mode, extern
323392
}
324393
}
325394
if backedKeys > 2 {
326-
return 0, "", false
395+
return state.KeyState{}, false
327396
}
328397

329398
// we have not migrated the latest key, do nothing until that is complete
330399
if allMigrated, _, _ := state.MigratedFor(encryptedGRs, latestKey); !allMigrated {
331-
return 0, "", false
400+
return state.KeyState{}, false
332401
}
333402

334403
// if the most recent secret was encrypted in a mode different than the current mode, we need to generate a new key
335404
if latestKey.Mode != currentMode {
336-
return latestKeyID, "encryption-mode-changed", true
405+
latestKey.InternalReason = "encryption-mode-changed"
406+
return latestKey, true
337407
}
338408

339409
// if the most recent secret turned off encryption and we want to keep it that way, do nothing
340410
if latestKey.Mode == state.Identity && currentMode == state.Identity {
341-
return 0, "", false
411+
return state.KeyState{}, false
412+
}
413+
414+
// if the hash of the kms config (kmsKeyID) has updated, we need a new KMS backing secret
415+
if currentMode == state.KMS && latestKey.KMSKeyID != optionalCurrentKMSKeyID {
416+
latestKey.InternalReason = "kms-config-changed"
417+
return latestKey, true
342418
}
343419

344420
// if the most recent secret has a different external reason than the current reason, we need to generate a new key
345421
if latestKey.ExternalReason != externalReason && len(externalReason) != 0 {
346-
return latestKeyID, "external-reason-changed", true
422+
latestKey.InternalReason = "external-reason-changed"
423+
return latestKey, true
347424
}
348425

349426
// we check for encryptionSecretMigratedTimestamp set by migration controller to determine when migration completed
350427
// this also generates back pressure for key rotation when migration takes a long time or was recently completed
351-
return latestKeyID, "rotation-interval-has-passed", time.Since(latestKey.Migrated.Timestamp) > encryptionSecretMigrationInterval
428+
latestKey.InternalReason = "rotation-interval-has-passed"
429+
return latestKey, time.Since(latestKey.Migrated.Timestamp) > encryptionSecretMigrationInterval
352430
}
353431

354432
// TODO make this un-settable once set

pkg/operator/encryption/controllers/key_controller_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import (
55
"encoding/base64"
66
"errors"
77
"fmt"
8-
clocktesting "k8s.io/utils/clock/testing"
98
"testing"
109
"time"
1110

11+
clocktesting "k8s.io/utils/clock/testing"
12+
1213
corev1 "k8s.io/api/core/v1"
1314
"k8s.io/apimachinery/pkg/api/equality"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -281,11 +282,11 @@ func TestKeyController(t *testing.T) {
281282
Type: "EncryptionKeyControllerDegraded",
282283
Status: "True",
283284
Reason: "Error",
284-
Message: "secret encryption-key-kms-1 is invalid, new keys cannot be created for encryption target",
285+
Message: "secret openshift-config-managed/encryption-key-kms-1 is invalid, new keys cannot be created for encryption target",
285286
}
286287
encryptiontesting.ValidateOperatorClientConditions(ts, operatorClient, []operatorv1.OperatorCondition{expectedCondition})
287288
},
288-
expectedError: errors.New("secret encryption-key-kms-1 is invalid, new keys cannot be created for encryption target"),
289+
expectedError: errors.New("secret openshift-config-managed/encryption-key-kms-1 is invalid, new keys cannot be created for encryption target"),
289290
},
290291

291292
{
@@ -494,7 +495,8 @@ func TestGetCurrentModeAndExternalReason(t *testing.T) {
494495

495496
// act
496497
target := keyController{unsupportedConfigPrefix: scenario.prefix, operatorClient: fakeOperatorClient, apiServerClient: fakeApiServerClient}
497-
_, externalReason, err := target.getCurrentModeAndExternalReason(context.TODO())
498+
ks, err := target.getCurrentEncryptionModeWithExternalReason(context.TODO())
499+
externalReason := ks.ExternalReason
498500

499501
// validate
500502
if err != nil {

pkg/operator/encryption/state/helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func KeysWithPotentiallyPersistedDataAndNextReadKey(grs []schema.GroupResource,
5151
return recentFirstSortedKeys
5252
}
5353

54+
// TODO: make sure sort takes into account for KMS keys
5455
func SortRecentFirst(unsorted []KeyState) []KeyState {
5556
ret := make([]KeyState, len(unsorted))
5657
copy(ret, unsorted)

0 commit comments

Comments
 (0)