1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00
kube-arangodb/pkg/deployment/reconcile/plan_builder_encryption.go
2023-12-12 16:39:35 +01:00

283 lines
7.7 KiB
Go

//
// DISCLAIMER
//
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package reconcile
import (
"context"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/actions"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
sharedReconcile "github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/strings"
)
func skipEncryptionPlan(spec api.DeploymentSpec, status api.DeploymentStatus) bool {
if !spec.RocksDB.IsEncrypted() {
return true
}
if i := status.CurrentImage; i == nil || !features.EncryptionRotation().Supported(i.ArangoDBVersion, i.Enterprise) {
return true
}
return false
}
func (r *Reconciler) createEncryptionKeyStatusPropagatedFieldUpdate(ctx context.Context, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext, w WithPlanBuilder, builders ...planBuilder) api.Plan {
if skipEncryptionPlan(spec, status) {
return nil
}
var plan api.Plan
for _, builder := range builders {
if !plan.IsEmpty() {
continue
}
if p := w.Apply(builder); !p.IsEmpty() {
plan = append(plan, p...)
}
}
if plan.IsEmpty() {
return nil
}
if len(plan) == 1 && plan[0].Type == api.ActionTypeEncryptionKeyPropagated {
return plan
}
if status.Hashes.Encryption.Propagated {
plan = append(api.Plan{
actions.NewClusterAction(api.ActionTypeEncryptionKeyPropagated, "Change propagated flag to false").AddParam(propagated, conditionFalse),
}, plan...)
}
return plan
}
func (r *Reconciler) createEncryptionKey(ctx context.Context, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) api.Plan {
if skipEncryptionPlan(spec, status) {
return nil
}
secret, exists := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(spec.RocksDB.Encryption.GetKeySecretName())
if !exists {
return nil
}
name, _, err := pod.GetEncryptionKeyFromSecret(secret)
if err != nil {
r.log.Err(err).Error("Unable to fetch encryption key")
return nil
}
if !exists {
return nil
}
keyfolder, exists := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(pod.GetEncryptionFolderSecretName(context.GetName()))
if !exists {
r.log.Error("Encryption key folder does not exist")
return nil
}
if len(keyfolder.Data) == 0 {
keyfolder.Data = map[string][]byte{}
}
if status.Hashes.Encryption.Propagated {
_, ok := keyfolder.Data[name]
if !ok {
return api.Plan{actions.NewClusterAction(api.ActionTypeEncryptionKeyAdd)}
}
}
plan, failed := r.areEncryptionKeysUpToDate(ctx, spec, status, context, keyfolder)
if !plan.IsEmpty() {
return plan
}
if !failed && !status.Hashes.Encryption.Propagated {
return api.Plan{
actions.NewClusterAction(api.ActionTypeEncryptionKeyPropagated, "Change propagated flag to true").AddParam(propagated, conditionTrue),
}
}
return api.Plan{}
}
func (r *Reconciler) createEncryptionKeyStatusUpdate(ctx context.Context, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) api.Plan {
if skipEncryptionPlan(spec, status) {
return nil
}
if r.createEncryptionKeyStatusUpdateRequired(spec, status, context) {
return api.Plan{actions.NewClusterAction(api.ActionTypeEncryptionKeyStatusUpdate)}
}
return nil
}
func (r *Reconciler) createEncryptionKeyStatusUpdateRequired(spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) bool {
if skipEncryptionPlan(spec, status) {
return false
}
keyfolder, exists := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(pod.GetEncryptionFolderSecretName(context.GetName()))
if !exists {
r.log.Error("Encryption key folder does not exist")
return false
}
keyHashes := secretKeysToListWithPrefix(keyfolder)
return !strings.CompareStringArray(keyHashes, status.Hashes.Encryption.Keys)
}
func (r *Reconciler) createEncryptionKeyCleanPlan(ctx context.Context, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) api.Plan {
if skipEncryptionPlan(spec, status) {
return nil
}
keyfolder, exists := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(pod.GetEncryptionFolderSecretName(context.GetName()))
if !exists {
r.log.Error("Encryption key folder does not exist")
return nil
}
if !status.Hashes.Encryption.Propagated {
return nil
}
var plan api.Plan
if len(keyfolder.Data) <= 1 {
return nil
}
secret, exists := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(spec.RocksDB.Encryption.GetKeySecretName())
if !exists {
return nil
}
name, _, err := pod.GetEncryptionKeyFromSecret(secret)
if err != nil {
return nil
}
if !exists {
return nil
}
if _, ok := keyfolder.Data[name]; !ok {
r.log.Err(err).Error("Key from encryption is not in keyfolder - do nothing")
return nil
}
for key := range keyfolder.Data {
if key != name {
plan = append(plan, actions.NewClusterAction(api.ActionTypeEncryptionKeyRemove).AddParam("key", key))
}
}
if !plan.IsEmpty() {
return plan
}
return api.Plan{}
}
func (r *Reconciler) areEncryptionKeysUpToDate(ctx context.Context, spec api.DeploymentSpec,
status api.DeploymentStatus, context PlanBuilderContext, folder *core.Secret) (plan api.Plan, failed bool) {
for _, group := range api.AllServerGroups {
if !pod.GroupEncryptionSupported(spec.Mode.Get(), group) {
continue
}
for _, m := range status.Members.MembersOfGroup(group) {
if updateRequired, failedMember := r.isEncryptionKeyUpToDate(ctx, status, context, group, m, folder); failedMember {
failed = true
continue
} else if updateRequired {
plan = append(plan, actions.NewAction(api.ActionTypeEncryptionKeyRefresh, group, sharedReconcile.WithPredefinedMember(m.ID)))
continue
}
}
}
return
}
func (r *Reconciler) isEncryptionKeyUpToDate(ctx context.Context, status api.DeploymentStatus,
planCtx PlanBuilderContext,
group api.ServerGroup, m api.MemberStatus,
folder *core.Secret) (updateRequired bool, failed bool) {
if m.Phase != api.MemberPhaseCreated {
return false, true
}
if i, ok := status.Images.GetByImageID(m.ImageID); !ok || !features.EncryptionRotation().Supported(i.ArangoDBVersion, i.Enterprise) {
return false, false
}
log := r.log.Str("group", group.AsRole()).Str("member", m.ID)
c, err := planCtx.GetMembersState().GetMemberClient(m.ID)
if err != nil {
log.Err(err).Warn("Unable to get client")
return false, true
}
client := client.NewClient(c.Connection(), log)
ctxChild, cancel := globals.GetGlobalTimeouts().ArangoD().WithTimeout(ctx)
defer cancel()
e, err := client.GetEncryption(ctxChild)
if err != nil {
log.Err(err).Error("Unable to fetch encryption keys")
return false, true
}
if !e.Result.KeysPresent(folder.Data) {
log.Info("Refresh of encryption keys required")
return true, false
}
return false, false
}