Skip to content

Commit f2d210a

Browse files
authored
[Feature] Improve encryption key rotation status (#571)
1 parent 80acfbb commit f2d210a

File tree

10 files changed

+269
-10
lines changed

10 files changed

+269
-10
lines changed

pkg/apis/deployment/v1/deployment_status.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type DeploymentStatus struct {
7070
// detect changes in secret values.
7171
SecretHashes *SecretHashes `json:"secret-hashes,omitempty"`
7272

73+
// CurrentEncryptionKeys keep list of currently applied encryption keys as SHA256 hash
74+
CurrentEncryptionKeyHashes DeploymentStatusEncryptionKeyHashes `json:"currentEncryptionKeyHashes,omitempty"`
75+
7376
// ForceStatusReload if set to true forces a reload of the status from the custom resource.
7477
ForceStatusReload *bool `json:"force-status-reload,omitempty"`
7578
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Adam Janikowski
21+
//
22+
23+
package v1
24+
25+
import "fmt"
26+
27+
type DeploymentStatusEncryptionKeyHashes []string
28+
29+
func (d DeploymentStatusEncryptionKeyHashes) Contains(hash string) bool {
30+
if len(d) == 0 {
31+
return false
32+
}
33+
34+
for _, h := range d {
35+
if h == hash {
36+
return true
37+
}
38+
}
39+
40+
return false
41+
}
42+
43+
func (d DeploymentStatusEncryptionKeyHashes) ContainsSHA256(hash string) bool {
44+
return d.Contains(fmt.Sprintf("sha256:%s", hash))
45+
}

pkg/apis/deployment/v1/plan.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const (
8585
ActionTypeEncryptionKeyRemove ActionType = "EncryptionKeyRemove"
8686
// ActionTypeEncryptionKeyRefresh refresh encryption keys
8787
ActionTypeEncryptionKeyRefresh ActionType = "EncryptionKeyRefresh"
88+
// ActionTypeEncryptionKeyStatusUpdate update status object with current encryption keys
89+
ActionTypeEncryptionKeyStatusUpdate ActionType = "EncryptionKeyStatusUpdate"
8890
)
8991

9092
const (

pkg/apis/deployment/v1/zz_generated.deepcopy.go

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/deployment/pod/encryption.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,31 @@ func GroupEncryptionSupported(mode api.DeploymentMode, group api.ServerGroup) bo
6060
}
6161
}
6262

63-
func GetEncryptionKey(secrets k8sutil.SecretInterface, name string) (string, []byte, error) {
63+
func GetEncryptionKey(secrets k8sutil.SecretInterface, name string) (string, []byte, bool, error) {
6464
keyfile, err := secrets.Get(name, meta.GetOptions{})
6565
if err != nil {
66-
return "", nil, errors.Wrapf(err, "Unable to fetch secret")
66+
if k8sutil.IsNotFound(err) {
67+
return "", nil, false, nil
68+
}
69+
return "", nil, false, errors.Wrapf(err, "Unable to fetch secret")
6770
}
6871

6972
if len(keyfile.Data) == 0 {
70-
return "", nil, errors.Errorf("Current encryption key is not valid")
73+
return "", nil, false, nil
7174
}
7275

7376
d, ok := keyfile.Data[constants.SecretEncryptionKey]
74-
if !ok || len(d) != 32 {
75-
return "", nil, errors.Errorf("Current encryption key is not valid")
77+
if !ok {
78+
return "", nil, false, nil
79+
}
80+
81+
if len(d) != 32 {
82+
return "", nil, false, errors.Errorf("Current encryption key is not valid")
7683
}
7784

7885
sha := fmt.Sprintf("%0x", sha256.Sum256(d))
7986

80-
return sha, d, nil
87+
return sha, d, true, nil
8188
}
8289

8390
func GetKeyfolderSecretName(name string) string {

pkg/deployment/reconcile/action_encryption_add.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,16 @@ func (a *encryptionKeyAddAction) Start(ctx context.Context) (bool, error) {
8282
secret = s
8383
}
8484

85-
sha, d, err := pod.GetEncryptionKey(a.actionCtx.SecretsInterface(), secret)
85+
sha, d, exists, err := pod.GetEncryptionKey(a.actionCtx.SecretsInterface(), secret)
8686
if err != nil {
8787
a.log.Error().Err(err).Msgf("Unable to fetch current encryption key")
8888
return true, nil
8989
}
9090

91+
if !exists {
92+
return true, nil
93+
}
94+
9195
p := patch.NewPatch()
9296
p.ItemAdd(patch.NewPath("data", sha), base64.StdEncoding.EncodeToString(d))
9397

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Adam Janikowski
21+
//
22+
23+
package reconcile
24+
25+
import (
26+
"context"
27+
"sort"
28+
29+
"github.com/arangodb/kube-arangodb/pkg/util"
30+
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
32+
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
33+
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
34+
"github.com/rs/zerolog"
35+
)
36+
37+
func init() {
38+
registerAction(api.ActionTypeEncryptionKeyStatusUpdate, newEncryptionKeyStatusUpdate)
39+
}
40+
41+
func newEncryptionKeyStatusUpdate(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
42+
a := &encryptionKeyStatusUpdateAction{}
43+
44+
a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout)
45+
46+
return a
47+
}
48+
49+
type encryptionKeyStatusUpdateAction struct {
50+
actionImpl
51+
52+
actionEmptyCheckProgress
53+
}
54+
55+
func (a *encryptionKeyStatusUpdateAction) Start(ctx context.Context) (bool, error) {
56+
if err := ensureEncryptionSupport(a.actionCtx); err != nil {
57+
a.log.Error().Err(err).Msgf("Action not supported")
58+
return true, nil
59+
}
60+
61+
f, err := a.actionCtx.SecretsInterface().Get(pod.GetKeyfolderSecretName(a.actionCtx.GetAPIObject().GetName()), meta.GetOptions{})
62+
if err != nil {
63+
a.log.Error().Err(err).Msgf("Unable to get folder info")
64+
return true, nil
65+
}
66+
67+
keys := make([]string, 0, len(f.Data))
68+
69+
for key := range f.Data {
70+
keys = append(keys, key)
71+
}
72+
73+
sort.Strings(keys)
74+
75+
keyHashes := util.PrefixStringArray(keys, "sha256:")
76+
77+
if err = a.actionCtx.WithStatusUpdate(func(s *api.DeploymentStatus) bool {
78+
if len(keyHashes) == 0 {
79+
if s.CurrentEncryptionKeyHashes != nil {
80+
s.CurrentEncryptionKeyHashes = nil
81+
return true
82+
}
83+
84+
return false
85+
}
86+
87+
if !util.CompareStringArray(keyHashes, s.CurrentEncryptionKeyHashes) {
88+
s.CurrentEncryptionKeyHashes = keyHashes
89+
return true
90+
}
91+
return false
92+
}); err != nil {
93+
return false, err
94+
}
95+
96+
return true, nil
97+
}

pkg/deployment/reconcile/plan_builder_encryption.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ package reconcile
2525
import (
2626
"context"
2727

28+
"github.com/arangodb/kube-arangodb/pkg/util"
29+
2830
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
2931
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
3032

@@ -43,12 +45,16 @@ func createEncryptionKey(ctx context.Context, log zerolog.Logger, spec api.Deplo
4345
return nil
4446
}
4547

46-
name, _, err := pod.GetEncryptionKey(context.SecretsInterface(), spec.RocksDB.Encryption.GetKeySecretName())
48+
name, _, exists, err := pod.GetEncryptionKey(context.SecretsInterface(), spec.RocksDB.Encryption.GetKeySecretName())
4749
if err != nil {
4850
log.Err(err).Msgf("Unable to fetch encryption key")
4951
return nil
5052
}
5153

54+
if !exists {
55+
return nil
56+
}
57+
5258
keyfolder, err := context.SecretsInterface().Get(pod.GetKeyfolderSecretName(context.GetName()), meta.GetOptions{})
5359
if err != nil {
5460
log.Err(err).Msgf("Unable to fetch encryption folder")
@@ -112,6 +118,18 @@ func createEncryptionKey(ctx context.Context, log zerolog.Logger, spec api.Deplo
112118
return plan
113119
}
114120

121+
currentKeys := make([]string, 0, len(keyfolder.Data))
122+
123+
for key := range keyfolder.Data {
124+
currentKeys = append(currentKeys, key)
125+
}
126+
127+
currentKeyHashes := util.PrefixStringArray(currentKeys, "sha256:")
128+
129+
if !util.CompareStringArray(currentKeyHashes, status.CurrentEncryptionKeyHashes) {
130+
return api.Plan{api.NewAction(api.ActionTypeEncryptionKeyStatusUpdate, api.ServerGroupUnknown, "")}
131+
}
132+
115133
return api.Plan{}
116134
}
117135

@@ -134,12 +152,16 @@ func cleanEncryptionKey(ctx context.Context, log zerolog.Logger, spec api.Deploy
134152
return nil
135153
}
136154

137-
name, _, err := pod.GetEncryptionKey(context.SecretsInterface(), spec.RocksDB.Encryption.GetKeySecretName())
155+
name, _, exists, err := pod.GetEncryptionKey(context.SecretsInterface(), spec.RocksDB.Encryption.GetKeySecretName())
138156
if err != nil {
139157
log.Err(err).Msgf("Unable to fetch encryption key")
140158
return nil
141159
}
142160

161+
if !exists {
162+
return nil
163+
}
164+
143165
if _, ok := keyfolder.Data[name]; !ok {
144166
log.Err(err).Msgf("Key from encryption is not in keyfolder - do nothing")
145167
return nil

pkg/deployment/reconcile/plan_builder_restore.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,17 @@ func createRestorePlanEncryption(ctx context.Context, log zerolog.Logger, spec a
8888
api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "").AddParam("secret", secret),
8989
}
9090
}
91-
name, _, err := pod.GetEncryptionKey(builderCtx.SecretsInterface(), secret)
91+
name, _, exists, err := pod.GetEncryptionKey(builderCtx.SecretsInterface(), secret)
9292
if err != nil {
9393
log.Err(err).Msgf("Unable to fetch encryption key")
9494
return nil
9595
}
9696

97+
if !exists {
98+
log.Error().Msgf("Unable to fetch encryption key - key is empty or missing")
99+
return nil
100+
}
101+
97102
if _, ok := keyfolder.Data[name]; !ok {
98103
log.Err(err).Msgf("Key from encryption is not in keyfolder")
99104

pkg/util/strings.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Adam Janikowski
21+
//
22+
23+
package util
24+
25+
import "fmt"
26+
27+
func CompareStringArray(a, b []string) bool {
28+
if len(a) != len(b) {
29+
return false
30+
}
31+
32+
for id, ak := range a {
33+
if ak != b[id] {
34+
return false
35+
}
36+
}
37+
38+
return true
39+
}
40+
41+
func PrefixStringArray(a []string, prefix string) []string {
42+
b := make([]string, len(a))
43+
44+
for id, element := range a {
45+
b[id] = fmt.Sprintf("%s%s", prefix, element)
46+
}
47+
48+
return b
49+
}

0 commit comments

Comments
 (0)