Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API-1509: Enable aes-gcm encryption provider #1469

Merged
merged 1 commit into from Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/operator/encryption/controllers/key_controller.go
Expand Up @@ -270,7 +270,7 @@ func (c *keyController) getCurrentModeAndExternalReason(ctx context.Context) (st

reason := encryptionConfig.Encryption.Reason
switch currentMode := state.Mode(apiServer.Spec.Encryption.Type); currentMode {
case state.AESCBC, state.Identity: // secretbox is disabled for now
case state.AESCBC, state.AESGCM, state.Identity: // secretbox is disabled for now
return currentMode, reason, nil
case "": // unspecified means use the default (which can change over time)
return state.DefaultMode, reason, nil
Expand Down
78 changes: 53 additions & 25 deletions pkg/operator/encryption/controllers/key_controller_test.go
Expand Up @@ -31,14 +31,13 @@ import (
)

func TestKeyController(t *testing.T) {
apiServerAesCBC := []runtime.Object{&configv1.APIServer{
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
Spec: configv1.APIServerSpec{
Encryption: configv1.APIServerEncryption{
Type: "aescbc",
},
},
}}
simpleAPIServer := &configv1.APIServer{ObjectMeta: metav1.ObjectMeta{Name: "cluster"}}

apiServerWithAESCBC := simpleAPIServer.DeepCopy()
apiServerWithAESCBC.Spec.Encryption = configv1.APIServerEncryption{Type: "aescbc"}

apiServerWithAESGCM := simpleAPIServer.DeepCopy()
apiServerWithAESGCM.Spec.Encryption = configv1.APIServerEncryption{Type: "aesgcm"}

scenarios := []struct {
name string
Expand Down Expand Up @@ -73,7 +72,7 @@ func TestKeyController(t *testing.T) {
},
targetNamespace: "kms",
initialObjects: []runtime.Object{},
apiServerObjects: []runtime.Object{&configv1.APIServer{ObjectMeta: metav1.ObjectMeta{Name: "cluster"}}},
apiServerObjects: []runtime.Object{simpleAPIServer},
validateFunc: func(ts *testing.T, actions []clientgotesting.Action, targetNamespace string, targetGRs []schema.GroupResource) {
},
expectedActions: []string{"list:pods:kms"},
Expand All @@ -86,7 +85,7 @@ func TestKeyController(t *testing.T) {
},
targetNamespace: "kms",
initialObjects: []runtime.Object{encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1")},
apiServerObjects: []runtime.Object{&configv1.APIServer{ObjectMeta: metav1.ObjectMeta{Name: "cluster"}}},
apiServerObjects: []runtime.Object{simpleAPIServer},
validateFunc: func(ts *testing.T, actions []clientgotesting.Action, targetNamespace string, targetGRs []schema.GroupResource) {
},
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed"},
Expand All @@ -104,14 +103,7 @@ func TestKeyController(t *testing.T) {
initialObjects: []runtime.Object{
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
},
apiServerObjects: []runtime.Object{&configv1.APIServer{
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
Spec: configv1.APIServerSpec{
Encryption: configv1.APIServerEncryption{
Type: "aescbc",
},
},
}},
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
validateFunc: func(ts *testing.T, actions []clientgotesting.Action, targetNamespace string, targetGRs []schema.GroupResource) {
wasSecretValidated := false
for _, action := range actions {
Expand Down Expand Up @@ -145,7 +137,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateEncryptionKeySecretWithRawKey("kms", nil, 7, []byte("61def964fb967f5d7c44a2af8dab6865")),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed"},
},
Expand All @@ -159,7 +151,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateMigratedEncryptionKeySecretWithRawKey("kms", []schema.GroupResource{{Group: "", Resource: "secrets"}}, 7, []byte("61def964fb967f5d7c44a2af8dab6865"), time.Now()),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed"},
},
Expand All @@ -173,7 +165,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateEncryptionKeySecretWithRawKey("kms", []schema.GroupResource{{Group: "", Resource: "secrets"}}, 7, []byte("61def964fb967f5d7c44a2af8dab6865")),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed", "create:secrets:openshift-config-managed", "create:events:kms"},
},
Expand All @@ -187,7 +179,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateExpiredMigratedEncryptionKeySecretWithRawKey("kms", []schema.GroupResource{{Group: "", Resource: "secrets"}}, 5, []byte("61def964fb967f5d7c44a2af8dab6865")),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed", "create:secrets:openshift-config-managed", "create:events:kms"},
validateFunc: func(ts *testing.T, actions []clientgotesting.Action, targetNamespace string, targetGRs []schema.GroupResource) {
Expand Down Expand Up @@ -245,7 +237,7 @@ func TestKeyController(t *testing.T) {
return ecs
}(),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{
"list:pods:kms",
Expand All @@ -266,7 +258,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateExpiredMigratedEncryptionKeySecretWithRawKey("kms", []schema.GroupResource{{Group: "", Resource: "secrets"}}, 5, []byte("61def964fb967f5d7c44a2af8dab6865")),
encryptiontesting.CreateEncryptionKeySecretWithRawKey("kms", nil, 6, []byte("61def964fb967f5d7c44a2af8dab6865")),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed"},
},
Expand All @@ -280,7 +272,7 @@ func TestKeyController(t *testing.T) {
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateEncryptionKeySecretWithRawKey("kms", nil, 1, []byte("")),
},
apiServerObjects: apiServerAesCBC,
apiServerObjects: []runtime.Object{apiServerWithAESCBC},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed", "create:secrets:openshift-config-managed", "get:secrets:openshift-config-managed"},
validateOperatorClientFunc: func(ts *testing.T, operatorClient v1helpers.OperatorClient) {
Expand All @@ -294,6 +286,42 @@ func TestKeyController(t *testing.T) {
},
expectedError: errors.New("secret encryption-key-kms-1 is invalid, new keys cannot be created for encryption target"),
},

{
name: "creates a new write key because the encryption mode changed",
targetGRs: []schema.GroupResource{
{Group: "", Resource: "secrets"},
},
initialObjects: []runtime.Object{
encryptiontesting.CreateDummyKubeAPIPod("kube-apiserver-1", "kms", "node-1"),
encryptiontesting.CreateEncryptionKeySecretWithRawKeyWithMode("kms", []schema.GroupResource{{Group: "", Resource: "secrets"}}, 5, []byte("61def964fb967f5d7c44a2af8dab6865"), "aescbc"),
},
apiServerObjects: []runtime.Object{apiServerWithAESGCM},
targetNamespace: "kms",
expectedActions: []string{"list:pods:kms", "get:secrets:kms", "list:secrets:openshift-config-managed", "create:secrets:openshift-config-managed", "create:events:kms"},
validateFunc: func(ts *testing.T, actions []clientgotesting.Action, targetNamespace string, targetGRs []schema.GroupResource) {
wasSecretValidated := false
for _, action := range actions {
if action.Matches("create", "secrets") {
createAction := action.(clientgotesting.CreateAction)
actualSecret := createAction.GetObject().(*corev1.Secret)
expectedSecret := encryptiontesting.CreateEncryptionKeySecretWithKeyFromExistingSecretWithMode(targetNamespace, []schema.GroupResource{}, 6, actualSecret, "aesgcm")
expectedSecret.Annotations["encryption.apiserver.operator.openshift.io/internal-reason"] = "secrets-encryption-mode-changed"
if !equality.Semantic.DeepEqual(actualSecret, expectedSecret) {
ts.Errorf(diff.ObjectDiff(expectedSecret, actualSecret))
}
if err := encryptiontesting.ValidateEncryptionKey(actualSecret); err != nil {
ts.Error(err)
}
wasSecretValidated = true
break
}
}
if !wasSecretValidated {
ts.Errorf("the secret wasn't created and validated")
}
},
},
}

for _, scenario := range scenarios {
Expand Down
1 change: 1 addition & 0 deletions pkg/operator/encryption/crypto/keys.go
Expand Up @@ -9,6 +9,7 @@ import (
var (
ModeToNewKeyFunc = map[state.Mode]func() []byte{
state.AESCBC: NewAES256Key,
state.AESGCM: NewAES256Key,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize it was exactly the same key. That's convenient.

state.SecretBox: NewAES256Key, // secretbox requires a 32 byte key so we can reuse the same function here
state.Identity: NewIdentityKey,
}
Expand Down
15 changes: 13 additions & 2 deletions pkg/operator/encryption/encryptionconfig/config.go
Expand Up @@ -95,10 +95,15 @@ func ToEncryptionState(encryptionConfig *apiserverconfigv1.EncryptionConfigurati
// skip fake provider. If this is write-key, wait for first aesgcm provider providing the write key.
continue

case provider.AESGCM != nil && len(provider.AESGCM.Keys) == 1 && provider.AESGCM.Keys[0].Secret == emptyStaticIdentityKey:
case provider.AESGCM != nil && len(provider.AESGCM.Keys) == 1:
s := state.AESGCM
if provider.AESGCM.Keys[0].Secret == emptyStaticIdentityKey {
s = state.Identity
}

ks = state.KeyState{
Key: provider.AESGCM.Keys[0],
Mode: state.Identity,
Mode: s,
}

default:
Expand Down Expand Up @@ -164,6 +169,12 @@ func stateToProviders(desired state.GroupResourceState) []apiserverconfigv1.Prov
Keys: []apiserverconfigv1.Key{key.Key},
},
})
case state.AESGCM:
providers = append(providers, apiserverconfigv1.ProviderConfiguration{
AESGCM: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key.Key},
},
})
case state.SecretBox:
providers = append(providers, apiserverconfigv1.ProviderConfiguration{
Secretbox: &apiserverconfigv1.SecretboxConfiguration{
Expand Down
34 changes: 34 additions & 0 deletions pkg/operator/encryption/encryptionconfig/config_test.go
Expand Up @@ -327,6 +327,40 @@ func TestToEncryptionState(t *testing.T) {

// scenario 9
// TODO: encryption on after being off

// scenario 10
{
name: "aes-gcm write key and aes-cbc read key",
input: func() *apiserverconfigv1.EncryptionConfiguration {
keysRes := encryptiontesting.EncryptionKeysResourceTuple{
Resource: "secrets",
Keys: []apiserverconfigv1.Key{
{
Name: "35",
Secret: "MTcxNTgyYTBmY2Q2YzVmZGI2NWNiZjVhM2U5MjQ5ZDc=",
},
{
Name: "34",
Secret: "MTcxNTgyYTBmY2Q2YzVmZGI2NWNiZjVhM2U5MjQ5ZDc=",
},
},
Modes: []string{"aesgcm", "aescbc"},
}
ec := encryptiontesting.CreateEncryptionCfgWithWriteKey([]encryptiontesting.EncryptionKeysResourceTuple{keysRes})
return ec
}(),
output: map[schema.GroupResource]state.GroupResourceState{
{Group: "", Resource: "secrets"}: {
WriteKey: state.KeyState{
Key: apiserverconfigv1.Key{Name: "35", Secret: "MTcxNTgyYTBmY2Q2YzVmZGI2NWNiZjVhM2U5MjQ5ZDc="}, Mode: "aesgcm",
},
ReadKeys: []state.KeyState{
{Key: apiserverconfigv1.Key{Name: "35", Secret: "MTcxNTgyYTBmY2Q2YzVmZGI2NWNiZjVhM2U5MjQ5ZDc="}, Mode: "aesgcm"},
{Key: apiserverconfigv1.Key{Name: "34", Secret: "MTcxNTgyYTBmY2Q2YzVmZGI2NWNiZjVhM2U5MjQ5ZDc="}, Mode: "aescbc"},
},
},
},
},
}

for _, scenario := range scenarios {
Expand Down
2 changes: 1 addition & 1 deletion pkg/operator/encryption/secrets/secrets.go
Expand Up @@ -60,7 +60,7 @@ func ToKeyState(s *corev1.Secret) (state.KeyState, error) {

keyMode := state.Mode(s.Annotations[encryptionSecretMode])
switch keyMode {
case state.AESCBC, state.SecretBox, state.Identity:
case state.AESCBC, state.AESGCM, state.SecretBox, state.Identity:
key.Mode = keyMode
default:
return state.KeyState{}, fmt.Errorf("secret %s/%s has invalid mode: %s", s.Namespace, s.Name, keyMode)
Expand Down
34 changes: 34 additions & 0 deletions pkg/operator/encryption/secrets/secrets_test.go
Expand Up @@ -55,6 +55,40 @@ func TestRoundtrip(t *testing.T) {
Mode: "aescbc",
},
},
{
name: "full aesgcm",
component: "kms",
ks: state.KeyState{
Key: v1.Key{
Name: "54",
Secret: base64.StdEncoding.EncodeToString([]byte("abcdef")),
},
Backed: true, // this will be set by ToKeyState()
Mode: "aesgcm",
Migrated: state.MigrationState{
Timestamp: now,
Resources: []schema.GroupResource{
{Resource: "secrets"},
{Resource: "configmaps"},
{Group: "networking.openshift.io", Resource: "routes"},
},
},
InternalReason: "internal",
ExternalReason: "external",
},
},
{
name: "sparse aesgcm",
component: "kms",
ks: state.KeyState{
Key: v1.Key{
Name: "54",
Secret: base64.StdEncoding.EncodeToString([]byte("abcdef")),
},
Backed: true, // this will be set by ToKeyState()
Mode: "aesgcm",
},
},
{
name: "identity",
component: "kms",
Expand Down
3 changes: 2 additions & 1 deletion pkg/operator/encryption/state/types.go
Expand Up @@ -56,7 +56,8 @@ type Mode string
// These values are encoded into the secret and thus must not be changed.
// Strings are used over iota because they are easier for a human to understand.
const (
AESCBC Mode = "aescbc" // available from the first release, see defaultMode below
AESCBC Mode = "aescbc" // available from the first release, see defaultMode below
AESGCM Mode = "aesgcm"
SecretBox Mode = "secretbox" // available from the first release, see defaultMode below
Identity Mode = "identity" // available from the first release, see defaultMode below

Expand Down
60 changes: 39 additions & 21 deletions pkg/operator/encryption/testing/helpers.go
Expand Up @@ -73,7 +73,11 @@ func CreateEncryptionKeySecretWithRawKeyWithMode(targetNS string, grs []schema.G
}

func CreateEncryptionKeySecretWithKeyFromExistingSecret(targetNS string, grs []schema.GroupResource, keyID uint64, existingSecret *corev1.Secret) *corev1.Secret {
secret := CreateEncryptionKeySecretNoData(targetNS, grs, keyID)
return CreateEncryptionKeySecretWithKeyFromExistingSecretWithMode(targetNS, grs, keyID, existingSecret, "aescbc")
}

func CreateEncryptionKeySecretWithKeyFromExistingSecretWithMode(targetNS string, grs []schema.GroupResource, keyID uint64, existingSecret *corev1.Secret, mode string) *corev1.Secret {
secret := CreateEncryptionKeySecretNoDataWithMode(targetNS, grs, keyID, mode)
if rawKey, exist := existingSecret.Data[encryptionSecretKeyDataForTest]; exist {
secret.Data[encryptionSecretKeyDataForTest] = rawKey
}
Expand Down Expand Up @@ -182,20 +186,7 @@ func CreateEncryptionCfgNoWriteKeyMultipleReadKeys(keysResources []EncryptionKey
if len(keysResource.Modes) == len(keysResource.Keys) {
desiredMode = keysResource.Modes[i]
}
switch desiredMode {
case "aesgcm":
rc.Providers = append(rc.Providers, apiserverconfigv1.ProviderConfiguration{
AESGCM: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
})
default:
rc.Providers = append(rc.Providers, apiserverconfigv1.ProviderConfiguration{
AESCBC: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
})
}
rc.Providers = append(rc.Providers, *createProviderCfg(desiredMode, key))
}
ec.Resources = append(ec.Resources, rc)
}
Expand All @@ -208,12 +199,12 @@ func CreateEncryptionCfgWithWriteKey(keysResources []EncryptionKeysResourceTuple
for _, keysResource := range keysResources {
// TODO allow secretbox -> not sure if EncryptionKeysResourceTuple makes sense
providers := []apiserverconfigv1.ProviderConfiguration{}
for _, key := range keysResource.Keys {
providers = append(providers, apiserverconfigv1.ProviderConfiguration{
AESCBC: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
})
for i, key := range keysResource.Keys {
desiredMode := ""
if len(keysResource.Modes) == len(keysResource.Keys) {
desiredMode = keysResource.Modes[i]
}
providers = append(providers, *createProviderCfg(desiredMode, key))
}
providers = append(providers, apiserverconfigv1.ProviderConfiguration{
Identity: &apiserverconfigv1.IdentityConfiguration{},
Expand All @@ -234,6 +225,33 @@ func CreateEncryptionCfgWithWriteKey(keysResources []EncryptionKeysResourceTuple
}
}

func createProviderCfg(mode string, key apiserverconfigv1.Key) *apiserverconfigv1.ProviderConfiguration {
switch mode {
case "aesgcm":
return &apiserverconfigv1.ProviderConfiguration{
AESGCM: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
}
case "secretbox":
return &apiserverconfigv1.ProviderConfiguration{
Secretbox: &apiserverconfigv1.SecretboxConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
}
case "identity":
return &apiserverconfigv1.ProviderConfiguration{
Identity: &apiserverconfigv1.IdentityConfiguration{},
}
default:
return &apiserverconfigv1.ProviderConfiguration{
AESCBC: &apiserverconfigv1.AESConfiguration{
Keys: []apiserverconfigv1.Key{key},
},
}
}
}

type EncryptionKeysResourceTuple struct {
Resource string
Keys []apiserverconfigv1.Key
Expand Down