From 82bdfc11517401b81fc5be0848804820901f73bf Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Mon, 13 Jul 2020 07:22:32 +0000 Subject: [PATCH 1/3] Update Propagated flags --- pkg/apis/deployment/v1/hashes.go | 4 + pkg/apis/deployment/v1/plan.go | 4 + pkg/deployment/pod/encryption.go | 4 +- .../reconcile/action_encryption_add.go | 2 +- .../reconcile/action_encryption_propagated.go | 71 +++++++++++ .../reconcile/action_tls_propagated.go | 71 +++++++++++ pkg/deployment/reconcile/plan_builder.go | 30 +++-- .../reconcile/plan_builder_encryption.go | 64 ++++++++-- .../reconcile/plan_builder_restore.go | 61 ++++----- pkg/deployment/reconcile/plan_builder_tls.go | 52 ++++++++ pkg/deployment/reconcile/plan_executor.go | 12 +- pkg/deployment/resources/exporter.go | 8 +- .../resources/inspector/inspector.go | 3 + pkg/deployment/resources/secrets.go | 116 ++++++++++++++---- pkg/util/k8sutil/pods.go | 1 + pkg/util/k8sutil/secrets.go | 16 ++- 16 files changed, 436 insertions(+), 83 deletions(-) create mode 100644 pkg/deployment/reconcile/action_encryption_propagated.go create mode 100644 pkg/deployment/reconcile/action_tls_propagated.go diff --git a/pkg/apis/deployment/v1/hashes.go b/pkg/apis/deployment/v1/hashes.go index 1f0105795..d3be90df3 100644 --- a/pkg/apis/deployment/v1/hashes.go +++ b/pkg/apis/deployment/v1/hashes.go @@ -32,11 +32,15 @@ type DeploymentStatusHashes struct { type DeploymentStatusHashesEncryption struct { Keys shared.HashList `json:"keys,omitempty"` + + Propagated bool `json:"propagated,omitempty"` } type DeploymentStatusHashesTLS struct { CA *string `json:"ca,omitempty"` Truststore shared.HashList `json:"truststore,omitempty"` + + Propagated bool `json:"propagated,omitempty"` } type DeploymentStatusHashesJWT struct { diff --git a/pkg/apis/deployment/v1/plan.go b/pkg/apis/deployment/v1/plan.go index ed3eb2f4f..2cd886c8c 100644 --- a/pkg/apis/deployment/v1/plan.go +++ b/pkg/apis/deployment/v1/plan.go @@ -75,6 +75,8 @@ const ( ActionTypeRefreshTLSKeyfileCertificate ActionType = "RefreshTLSKeyfileCertificate" // ActionTypeTLSKeyStatusUpdate update status with current data from deployment ActionTypeTLSKeyStatusUpdate ActionType = "TLSKeyStatusUpdate" + // ActionTypeTLSPropagated change propagated flag + ActionTypeTLSPropagated ActionType = "TLSPropagated" // ActionTypeUpdateTLSSNI update SNI inplace. ActionTypeUpdateTLSSNI ActionType = "UpdateTLSSNI" // ActionTypeSetCurrentImage causes status.CurrentImage to be updated to the image given in the action. @@ -101,6 +103,8 @@ const ( ActionTypeEncryptionKeyRefresh ActionType = "EncryptionKeyRefresh" // ActionTypeEncryptionKeyStatusUpdate update status object with current encryption keys ActionTypeEncryptionKeyStatusUpdate ActionType = "EncryptionKeyStatusUpdate" + // ActionTypeEncryptionKeyPropagated change propagated flag + ActionTypeEncryptionKeyPropagated ActionType = "EncryptionKeyPropagated" // ActionTypeJWTStatusUpdate update status of JWT Secret ActionTypeJWTStatusUpdate ActionType = "JWTStatusUpdate" // ActionTypeJWTSetActive change active JWT key diff --git a/pkg/deployment/pod/encryption.go b/pkg/deployment/pod/encryption.go index a03c8274b..67e2abb5b 100644 --- a/pkg/deployment/pod/encryption.go +++ b/pkg/deployment/pod/encryption.go @@ -78,12 +78,12 @@ func GetEncryptionKey(secrets k8sutil.SecretInterface, name string) (string, []b func GetEncryptionKeyFromSecret(keyfile *core.Secret) (string, []byte, error) { if len(keyfile.Data) == 0 { - return "", nil, nil + return "", nil, errors.Errorf("Current encryption key is not valid - missing data section") } d, ok := keyfile.Data[constants.SecretEncryptionKey] if !ok { - return "", nil, nil + return "", nil, errors.Errorf("Current encryption key is not valid - missing field") } if len(d) != 32 { diff --git a/pkg/deployment/reconcile/action_encryption_add.go b/pkg/deployment/reconcile/action_encryption_add.go index 775f03e16..027daf908 100644 --- a/pkg/deployment/reconcile/action_encryption_add.go +++ b/pkg/deployment/reconcile/action_encryption_add.go @@ -78,7 +78,7 @@ func (a *encryptionKeyAddAction) Start(ctx context.Context) (bool, error) { } secret := a.actionCtx.GetSpec().RocksDB.Encryption.GetKeySecretName() - if s, ok := a.action.Params["secret"]; ok { + if s, ok := a.action.Params[secretActionParam]; ok { secret = s } diff --git a/pkg/deployment/reconcile/action_encryption_propagated.go b/pkg/deployment/reconcile/action_encryption_propagated.go new file mode 100644 index 000000000..45aad362a --- /dev/null +++ b/pkg/deployment/reconcile/action_encryption_propagated.go @@ -0,0 +1,71 @@ +// +// DISCLAIMER +// +// Copyright 2020 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 +// +// Author Adam Janikowski +// + +package reconcile + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/rs/zerolog" +) + +func init() { + registerAction(api.ActionTypeEncryptionKeyPropagated, newEncryptionKeyPropagated) +} + +func newEncryptionKeyPropagated(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &encryptionKeyPropagatedAction{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout) + + return a +} + +type encryptionKeyPropagatedAction struct { + actionImpl + + actionEmptyCheckProgress +} + +func (a *encryptionKeyPropagatedAction) Start(ctx context.Context) (bool, error) { + propagatedFlag, exists := a.action.Params[propagated] + if !exists { + a.log.Error().Msgf("Propagated flag is missing") + return true, nil + } + + propagatedFlagBool := propagatedFlag == conditionTrue + + if err := a.actionCtx.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + if s.Hashes.Encryption.Propagated != propagatedFlagBool { + s.Hashes.Encryption.Propagated = propagatedFlagBool + return true + } + + return false + }); err != nil { + return false, err + } + + return true, nil +} diff --git a/pkg/deployment/reconcile/action_tls_propagated.go b/pkg/deployment/reconcile/action_tls_propagated.go new file mode 100644 index 000000000..d2f2fa7a4 --- /dev/null +++ b/pkg/deployment/reconcile/action_tls_propagated.go @@ -0,0 +1,71 @@ +// +// DISCLAIMER +// +// Copyright 2020 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 +// +// Author Adam Janikowski +// + +package reconcile + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/rs/zerolog" +) + +func init() { + registerAction(api.ActionTypeTLSPropagated, newTLSPropagated) +} + +func newTLSPropagated(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action { + a := &tlsPropagatedAction{} + + a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout) + + return a +} + +type tlsPropagatedAction struct { + actionImpl + + actionEmptyCheckProgress +} + +func (a *tlsPropagatedAction) Start(ctx context.Context) (bool, error) { + propagatedFlag, exists := a.action.Params[propagated] + if !exists { + a.log.Error().Msgf("Propagated flag is missing") + return true, nil + } + + propagatedFlagBool := propagatedFlag == conditionTrue + + if err := a.actionCtx.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + if s.Hashes.TLS.Propagated != propagatedFlagBool { + s.Hashes.TLS.Propagated = propagatedFlagBool + return true + } + + return false + }); err != nil { + return false, err + } + + return true, nil +} diff --git a/pkg/deployment/reconcile/plan_builder.go b/pkg/deployment/reconcile/plan_builder.go index 9bbb49ba1..a467ee7b0 100644 --- a/pkg/deployment/reconcile/plan_builder.go +++ b/pkg/deployment/reconcile/plan_builder.go @@ -203,7 +203,7 @@ func createPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIOb // Update status if plan.IsEmpty() { - plan = pb.Apply(createEncryptionKeyStatusUpdate) + plan = pb.ApplySubPlan(createEncryptionKeyStatusPropagatedFieldUpdate, createEncryptionKeyStatusUpdate) } if plan.IsEmpty() { @@ -226,7 +226,7 @@ func createPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIOb // Add keys if plan.IsEmpty() { - plan = pb.Apply(createEncryptionKey) + plan = pb.ApplySubPlan(createEncryptionKeyStatusPropagatedFieldUpdate, createEncryptionKey) } if plan.IsEmpty() { @@ -234,11 +234,11 @@ func createPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIOb } if plan.IsEmpty() { - plan = pb.Apply(createCARenewalPlan) + plan = pb.ApplySubPlan(createTLSStatusPropagatedFieldUpdate, createCARenewalPlan) } if plan.IsEmpty() { - plan = pb.Apply(createCAAppendPlan) + plan = pb.ApplySubPlan(createTLSStatusPropagatedFieldUpdate, createCAAppendPlan) } if plan.IsEmpty() { @@ -251,7 +251,7 @@ func createPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIOb } if plan.IsEmpty() { - plan = pb.Apply(createRotateTLSServerSNIPlan) + plan = pb.ApplySubPlan(createTLSStatusPropagatedFieldUpdate, createRotateTLSServerSNIPlan) } if plan.IsEmpty() { @@ -259,17 +259,23 @@ func createPlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIOb } if plan.IsEmpty() { - plan = pb.Apply(createEncryptionKeyCleanPlan) + plan = pb.ApplySubPlan(createEncryptionKeyStatusPropagatedFieldUpdate, createEncryptionKeyCleanPlan) } if plan.IsEmpty() { - plan = pb.Apply(createCACleanPlan) + plan = pb.ApplySubPlan(createTLSStatusPropagatedFieldUpdate, createCACleanPlan) } if plan.IsEmpty() { plan = pb.Apply(createClusterOperationPlan) } + // Final + + if plan.IsEmpty() { + plan = pb.Apply(createTLSStatusPropagated) + } + // Return plan return plan, true } @@ -296,6 +302,11 @@ type planBuilder func(ctx context.Context, spec api.DeploymentSpec, status api.DeploymentStatus, cachedStatus inspector.Inspector, context PlanBuilderContext) api.Plan +type planBuilderSubPlan func(ctx context.Context, + log zerolog.Logger, apiObject k8sutil.APIObject, + spec api.DeploymentSpec, status api.DeploymentStatus, + cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, plans ... planBuilder) api.Plan + func NewWithPlanBuilder(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, @@ -313,6 +324,7 @@ func NewWithPlanBuilder(ctx context.Context, type WithPlanBuilder interface { Apply(p planBuilder) api.Plan + ApplySubPlan(p planBuilderSubPlan, plans ... planBuilder) api.Plan } type withPlanBuilder struct { @@ -325,6 +337,10 @@ type withPlanBuilder struct { context PlanBuilderContext } +func (w withPlanBuilder) ApplySubPlan(p planBuilderSubPlan, plans ...planBuilder) api.Plan { + return p(w.ctx, w.log, w.apiObject, w.spec, w.status, w.cachedStatus, w.context, w, plans...) +} + func (w withPlanBuilder) Apply(p planBuilder) api.Plan { return p(w.ctx, w.log, w.apiObject, w.spec, w.status, w.cachedStatus, w.context) } diff --git a/pkg/deployment/reconcile/plan_builder_encryption.go b/pkg/deployment/reconcile/plan_builder_encryption.go index c41ee51fa..2a2283055 100644 --- a/pkg/deployment/reconcile/plan_builder_encryption.go +++ b/pkg/deployment/reconcile/plan_builder_encryption.go @@ -52,6 +52,43 @@ func skipEncryptionPlan(spec api.DeploymentSpec, status api.DeploymentStatus) bo return false } +func createEncryptionKeyStatusPropagatedFieldUpdate(ctx context.Context, + log zerolog.Logger, apiObject k8sutil.APIObject, + spec api.DeploymentSpec, status api.DeploymentStatus, + cachedStatus inspector.Inspector, 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{ + api.NewAction(api.ActionTypeEncryptionKeyPropagated, api.ServerGroupUnknown, "", "Change propagated flag to false").AddParam(propagated, conditionFalse), + }, plan...) + } + + return plan +} + func createEncryptionKey(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, @@ -85,17 +122,24 @@ func createEncryptionKey(ctx context.Context, keyfolder.Data = map[string][]byte{} } - _, ok := keyfolder.Data[name] - if !ok { - return api.Plan{api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "")} + if status.Hashes.Encryption.Propagated { + _, ok := keyfolder.Data[name] + if !ok { + return api.Plan{api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "")} + } } - plan, _ := areEncryptionKeysUpToDate(ctx, log, apiObject, spec, status, cachedStatus, context, keyfolder) - + plan, failed := areEncryptionKeysUpToDate(ctx, log, apiObject, spec, status, cachedStatus, context, keyfolder) if !plan.IsEmpty() { return plan } + if !failed && !status.Hashes.Encryption.Propagated { + return api.Plan{ + api.NewAction(api.ActionTypeEncryptionKeyPropagated, api.ServerGroupUnknown, "", "Change propagated flag to true").AddParam(propagated, conditionTrue), + } + } + return api.Plan{} } @@ -152,17 +196,11 @@ func createEncryptionKeyCleanPlan(ctx context.Context, return nil } - plan, failed := areEncryptionKeysUpToDate(ctx, log, apiObject, spec, status, cachedStatus, context, keyfolder) - - if failed { - log.Info().Msgf("Unable to continue with encryption until all servers are ready") + if !status.Hashes.Encryption.Propagated { return nil } - if len(plan) != 0 { - log.Info().Msgf("Unable to continue with encryption until all servers report state or gonna be upToDate") - return nil - } + var plan api.Plan if len(keyfolder.Data) <= 1 { return nil diff --git a/pkg/deployment/reconcile/plan_builder_restore.go b/pkg/deployment/reconcile/plan_builder_restore.go index 4491d4370..ddc08d7e0 100644 --- a/pkg/deployment/reconcile/plan_builder_restore.go +++ b/pkg/deployment/reconcile/plan_builder_restore.go @@ -29,13 +29,13 @@ import ( "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" backupv1 "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1" - "github.com/arangodb/kube-arangodb/pkg/deployment/pod" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/arangodb/kube-arangodb/pkg/deployment/pod" "github.com/rs/zerolog" ) +const secretActionParam = "secret" + func createRestorePlan(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, @@ -53,15 +53,24 @@ func createRestorePlan(ctx context.Context, return nil } - if p := createRestorePlanEncryption(ctx, log, spec, status, context, backup); !p.IsEmpty() { - return p - } - if backup.Status.Backup == nil { log.Warn().Msg("Backup not yet ready") return nil } + if spec.RocksDB.IsEncrypted() { + if ok, p := createRestorePlanEncryption(ctx, log, spec, status, context, backup); !ok { + return nil + } else if !p.IsEmpty() { + return p + } + + if !status.Hashes.Encryption.Propagated { + log.Warn().Msg("Backup not able to be restored in non propagated state") + return nil + } + } + return api.Plan{ api.NewAction(api.ActionTypeBackupRestore, api.ServerGroupUnknown, ""), } @@ -70,51 +79,43 @@ func createRestorePlan(ctx context.Context, return nil } -func createRestorePlanEncryption(ctx context.Context, log zerolog.Logger, spec api.DeploymentSpec, status api.DeploymentStatus, builderCtx PlanBuilderContext, backup *backupv1.ArangoBackup) api.Plan { +func createRestorePlanEncryption(ctx context.Context, log zerolog.Logger, spec api.DeploymentSpec, status api.DeploymentStatus, builderCtx PlanBuilderContext, backup *backupv1.ArangoBackup) (bool, api.Plan) { if spec.RestoreEncryptionSecret != nil { if !spec.RocksDB.IsEncrypted() { - return nil + return true, nil } if i := status.CurrentImage; i == nil || !i.Enterprise || i.ArangoDBVersion.CompareTo("3.7.0") < 0 { - return nil + return true, nil + } + + if !status.Hashes.Encryption.Propagated { + return false, nil } secret := *spec.RestoreEncryptionSecret // Additional logic to do restore with encryption key - keyfolder, err := builderCtx.SecretsInterface().Get(pod.GetEncryptionFolderSecretName(builderCtx.GetName()), meta.GetOptions{}) - if err != nil { - log.Err(err).Msgf("Unable to fetch encryption folder") - return nil - } - - if len(keyfolder.Data) == 0 { - return api.Plan{ - api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "").AddParam("secret", secret), - } - } name, _, exists, err := pod.GetEncryptionKey(builderCtx.SecretsInterface(), secret) if err != nil { log.Err(err).Msgf("Unable to fetch encryption key") - return nil + return false, nil } if !exists { log.Error().Msgf("Unable to fetch encryption key - key is empty or missing") - return nil + return false, nil } - if _, ok := keyfolder.Data[name]; !ok { - log.Err(err).Msgf("Key from encryption is not in keyfolder") - - return api.Plan{ - api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "").AddParam("secret", secret), + if !status.Hashes.Encryption.Keys.ContainsSHA256(name) { + return true, api.Plan{ + api.NewAction(api.ActionTypeEncryptionKeyPropagated, api.ServerGroupUnknown, "").AddParam(propagated, conditionFalse), + api.NewAction(api.ActionTypeEncryptionKeyAdd, api.ServerGroupUnknown, "").AddParam(secretActionParam, secret), } } - return nil + return true,nil } - return nil + return true,nil } diff --git a/pkg/deployment/reconcile/plan_builder_tls.go b/pkg/deployment/reconcile/plan_builder_tls.go index 306be0ba2..a161fda04 100644 --- a/pkg/deployment/reconcile/plan_builder_tls.go +++ b/pkg/deployment/reconcile/plan_builder_tls.go @@ -45,6 +45,40 @@ import ( const CertificateRenewalMargin = 7 * 24 * time.Hour + +func createTLSStatusPropagatedFieldUpdate(ctx context.Context, + log zerolog.Logger, apiObject k8sutil.APIObject, + spec api.DeploymentSpec, status api.DeploymentStatus, + cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, builders... planBuilder) api.Plan { + if !spec.TLS.IsSecure() { + 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 status.Hashes.TLS.Propagated { + plan = append(api.Plan{ + api.NewAction(api.ActionTypeTLSPropagated, api.ServerGroupUnknown, "", "Change propagated flag to false").AddParam(propagated, conditionFalse), + }, plan...) + } + + return plan +} + // createTLSStatusUpdate creates plan to update ca info func createTLSStatusUpdate(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, @@ -61,6 +95,24 @@ func createTLSStatusUpdate(ctx context.Context, return nil } +// createTLSStatusUpdate creates plan to update ca info +func createTLSStatusPropagated(ctx context.Context, + log zerolog.Logger, apiObject k8sutil.APIObject, + spec api.DeploymentSpec, status api.DeploymentStatus, + cachedStatus inspector.Inspector, context PlanBuilderContext) api.Plan { + if !spec.TLS.IsSecure() { + return nil + } + + if !status.Hashes.TLS.Propagated { + return api.Plan{ + api.NewAction(api.ActionTypeTLSPropagated, api.ServerGroupUnknown, "", "Change propagated flag to true").AddParam(propagated, conditionTrue), + } + } + + return nil +} + func createTLSStatusUpdateRequired(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, diff --git a/pkg/deployment/reconcile/plan_executor.go b/pkg/deployment/reconcile/plan_executor.go index 6e1ddd9b6..b1143eac6 100644 --- a/pkg/deployment/reconcile/plan_executor.go +++ b/pkg/deployment/reconcile/plan_executor.go @@ -56,13 +56,19 @@ func (d *Reconciler) ExecutePlan(ctx context.Context, cachedStatus inspector.Ins // Take first action planAction := loopStatus.Plan[0] - log := log.With(). + logContext := log.With(). Int("plan-len", len(loopStatus.Plan)). Str("action-id", planAction.ID). Str("action-type", string(planAction.Type)). Str("group", planAction.Group.AsRole()). - Str("member-id", planAction.MemberID). - Logger() + Str("member-id", planAction.MemberID) + + for k, v := range planAction.Params { + logContext = logContext.Str(k, v) + } + + log := logContext.Logger() + action := d.createAction(ctx, log, planAction, cachedStatus) if planAction.StartTime.IsZero() { // Not started yet diff --git a/pkg/deployment/resources/exporter.go b/pkg/deployment/resources/exporter.go index b90b22b25..bb2b21b4f 100644 --- a/pkg/deployment/resources/exporter.go +++ b/pkg/deployment/resources/exporter.go @@ -71,19 +71,19 @@ func createExporterArgs(spec api.DeploymentSpec) []string { scheme = "https" } options = append(options, - k8sutil.OptionPair{"--arangodb.jwt-file", tokenpath}, - k8sutil.OptionPair{"--arangodb.endpoint", scheme + "://localhost:" + strconv.Itoa(k8sutil.ArangoPort)}, + k8sutil.OptionPair{Key: "--arangodb.jwt-file", Value: tokenpath}, + k8sutil.OptionPair{Key: "--arangodb.endpoint", Value: scheme + "://localhost:" + strconv.Itoa(k8sutil.ArangoPort)}, ) keyPath := filepath.Join(k8sutil.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile) if spec.IsSecure() { options = append(options, - k8sutil.OptionPair{"--ssl.keyfile", keyPath}, + k8sutil.OptionPair{Key: "--ssl.keyfile", Value: keyPath}, ) } if port := spec.Metrics.GetPort(); port != k8sutil.ArangoExporterPort { options = append(options, - k8sutil.OptionPair{"--server.address", fmt.Sprintf(":%d", port)}, + k8sutil.OptionPair{Key: "--server.address", Value: fmt.Sprintf(":%d", port)}, ) } diff --git a/pkg/deployment/resources/inspector/inspector.go b/pkg/deployment/resources/inspector/inspector.go index d4952c14a..9f88d54a1 100644 --- a/pkg/deployment/resources/inspector/inspector.go +++ b/pkg/deployment/resources/inspector/inspector.go @@ -115,6 +115,9 @@ type inspector struct { services map[string]*core.Service serviceAccounts map[string]*core.ServiceAccount podDisruptionBudgets map[string]*policy.PodDisruptionBudget + + ns string + k kubernetes.Interface } func (i *inspector) Refresh(k kubernetes.Interface, namespace string) error { diff --git a/pkg/deployment/resources/secrets.go b/pkg/deployment/resources/secrets.go index 150440985..500b4d60c 100644 --- a/pkg/deployment/resources/secrets.go +++ b/pkg/deployment/resources/secrets.go @@ -27,6 +27,9 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "github.com/arangodb/kube-arangodb/pkg/deployment/patch" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" "time" "github.com/arangodb/kube-arangodb/pkg/util" @@ -42,14 +45,12 @@ import ( "github.com/arangodb/kube-arangodb/pkg/deployment/pod" "github.com/pkg/errors" - "github.com/arangodb/kube-arangodb/pkg/util/constants" - jg "github.com/dgrijalva/jwt-go" - "k8s.io/apimachinery/pkg/api/equality" - apierrors "k8s.io/apimachinery/pkg/api/errors" - api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/metrics" + "github.com/arangodb/kube-arangodb/pkg/util/constants" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" + jg "github.com/dgrijalva/jwt-go" + "k8s.io/apimachinery/pkg/api/equality" ) var ( @@ -89,15 +90,21 @@ func (r *Resources) EnsureSecrets(log zerolog.Logger, cachedStatus inspector.Ins if imageFound { if pod.VersionHasJWTSecretKeyfolder(image.ArangoDBVersion, image.Enterprise) { - if err := r.refreshCache(cachedStatus, r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName))); err != nil { + if err :=r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName)); err != nil { return maskAny(err) } } } if spec.Metrics.IsEnabled() { - if err := r.refreshCache(cachedStatus, r.ensureExporterTokenSecret(cachedStatus, secrets, spec.Metrics.GetJWTTokenSecretName(), spec.Authentication.GetJWTSecretName())); err != nil { - return maskAny(err) + if imageFound && pod.VersionHasJWTSecretKeyfolder(image.ArangoDBVersion, image.Enterprise) { + if err := r.refreshCache(cachedStatus, r.ensureExporterTokenSecret(cachedStatus, secrets, spec.Metrics.GetJWTTokenSecretName(), pod.JWTSecretFolder(deploymentName))); err != nil { + return maskAny(err) + } + } else { + if err := r.refreshCache(cachedStatus, r.ensureExporterTokenSecret(cachedStatus, secrets, spec.Metrics.GetJWTTokenSecretName(), spec.Authentication.GetJWTSecretName())); err != nil { + return maskAny(err) + } } } } @@ -188,7 +195,67 @@ func (r *Resources) refreshCache(cachedStatus inspector.Inspector, err error) er } func (r *Resources) ensureTokenSecretFolder(cachedStatus inspector.Inspector, secrets k8sutil.SecretInterface, secretName, folderSecretName string) error { - if _, exists := cachedStatus.Secret(folderSecretName); exists { + if f, exists := cachedStatus.Secret(folderSecretName); exists { + if len(f.Data) == 0 { + s, exists := cachedStatus.Secret(secretName) + if !exists { + return errors.Errorf("Token secret does not exist") + } + + token, ok := s.Data[constants.SecretKeyToken] + if !ok { + return errors.Errorf("Token secret is invalid") + } + + f.Data[util.SHA256(token)] = token + f.Data[pod.ActiveJWTKey] = token + f.Data[constants.SecretKeyToken] = token + + if _, err := secrets.Update(f); err != nil { + return err + } + + return operatorErrors.Reconcile() + } + + if _, ok := f.Data[pod.ActiveJWTKey]; !ok { + _, b, ok := getFirstKeyFromMap(f.Data) + if !ok { + return errors.Errorf("Token Folder secret is invalid") + } + + p := patch.NewPatch() + p.ItemAdd(patch.NewPath("data", pod.ActiveJWTKey), util.SHA256(b)) + + pdata, err := json.Marshal(p) + if err != nil { + return err + } + + if _, err := secrets.Patch(folderSecretName, types.JSONPatchType, pdata); err != nil { + return err + } + } + + if _, ok := f.Data[constants.SecretKeyToken]; !ok { + b, ok := f.Data[pod.ActiveJWTKey] + if !ok { + return errors.Errorf("Token Folder secret is invalid") + } + + p := patch.NewPatch() + p.ItemAdd(patch.NewPath("data", constants.SecretKeyToken), util.SHA256(b)) + + pdata, err := json.Marshal(p) + if err != nil { + return err + } + + if _, err := secrets.Patch(folderSecretName, types.JSONPatchType, pdata); err != nil { + return err + } + } + return nil } @@ -379,24 +446,21 @@ var ( // ensureExporterTokenSecret checks if a secret with given name exists in the namespace // of the deployment. If not, it will add such a secret with correct access. func (r *Resources) ensureExporterTokenSecret(cachedStatus inspector.Inspector, secrets k8sutil.SecretInterface, tokenSecretName, secretSecretName string) error { - if recreate, exists, err := r.ensureExporterTokenSecretCreateRequired(cachedStatus, tokenSecretName, secretSecretName); err != nil { + if update, exists, err := r.ensureExporterTokenSecretCreateRequired(cachedStatus, tokenSecretName, secretSecretName); err != nil { return err - } else if recreate { + } else if update { // Create secret - if exists { - if err := secrets.Delete(tokenSecretName, nil); err != nil && !apierrors.IsNotFound(err) { - return err + if !exists { + owner := r.context.GetAPIObject().AsOwner() + if err := k8sutil.CreateJWTFromSecret(secrets, tokenSecretName, secretSecretName, exporterTokenClaims, &owner); k8sutil.IsAlreadyExists(err) { + // Secret added while we tried it also + return nil + } else if err != nil { + // Failed to create secret + return maskAny(err) } } - owner := r.context.GetAPIObject().AsOwner() - if err := k8sutil.CreateJWTFromSecret(secrets, tokenSecretName, secretSecretName, exporterTokenClaims, &owner); k8sutil.IsAlreadyExists(err) { - // Secret added while we tried it also - return nil - } else if err != nil { - // Failed to create secret - return maskAny(err) - } return operatorErrors.Reconcile() } @@ -563,3 +627,11 @@ func (r *Resources) getSyncMonitoringToken(spec api.DeploymentSpec) (string, err } return s, nil } + +func getFirstKeyFromMap(m map[string][]byte) (string, []byte, bool) { + for k,v := range m { + return k,v,true + } + + return "",nil,false +} diff --git a/pkg/util/k8sutil/pods.go b/pkg/util/k8sutil/pods.go index 3a77c9165..776afd7ce 100644 --- a/pkg/util/k8sutil/pods.go +++ b/pkg/util/k8sutil/pods.go @@ -228,6 +228,7 @@ func ExporterJWTVolumeMount() core.VolumeMount { return core.VolumeMount{ Name: ExporterJWTVolumeName, MountPath: ExporterJWTVolumeMountDir, + ReadOnly:true, } } diff --git a/pkg/util/k8sutil/secrets.go b/pkg/util/k8sutil/secrets.go index 9461163cd..28656f0d6 100644 --- a/pkg/util/k8sutil/secrets.go +++ b/pkg/util/k8sutil/secrets.go @@ -273,10 +273,24 @@ func CreateTokenSecret(secrets SecretInterface, secretName, token string, ownerR return nil } +// CreateJWTTokenFromSecret creates a JWT token +func CreateJWTTokenFromSecret(secret string, claims map[string]interface{}) (string, error) { + // Create a new token object, specifying signing method and the claims + // you would like it to contain. + token := jg.NewWithClaims(jg.SigningMethodHS256, jg.MapClaims(claims)) + + // Sign and get the complete encoded token as a string using the secret + signedToken, err := token.SignedString([]byte(secret)) + if err != nil { + return "", maskAny(err) + } + + return signedToken, nil +} + // CreateJWTFromSecret creates a JWT using the secret stored in secretSecretName and stores the // result in a new secret called tokenSecretName func CreateJWTFromSecret(secrets SecretInterface, tokenSecretName, secretSecretName string, claims map[string]interface{}, ownerRef *meta.OwnerReference) error { - secret, err := GetTokenSecret(secrets, secretSecretName) if err != nil { return maskAny(err) From 29faf45a3586ad1ea04d013d64a8bcc9883e865f Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Tue, 14 Jul 2020 07:32:40 +0000 Subject: [PATCH 2/3] FMT --- pkg/deployment/reconcile/plan_builder.go | 4 ++-- pkg/deployment/reconcile/plan_builder_encryption.go | 4 ++-- pkg/deployment/reconcile/plan_builder_restore.go | 4 ++-- pkg/deployment/reconcile/plan_builder_tls.go | 3 +-- pkg/deployment/resources/inspector/inspector.go | 2 +- pkg/deployment/resources/secrets.go | 12 ++++++------ pkg/util/k8sutil/pods.go | 2 +- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pkg/deployment/reconcile/plan_builder.go b/pkg/deployment/reconcile/plan_builder.go index a467ee7b0..8bda7d708 100644 --- a/pkg/deployment/reconcile/plan_builder.go +++ b/pkg/deployment/reconcile/plan_builder.go @@ -305,7 +305,7 @@ type planBuilder func(ctx context.Context, type planBuilderSubPlan func(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, - cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, plans ... planBuilder) api.Plan + cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, plans ...planBuilder) api.Plan func NewWithPlanBuilder(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, @@ -324,7 +324,7 @@ func NewWithPlanBuilder(ctx context.Context, type WithPlanBuilder interface { Apply(p planBuilder) api.Plan - ApplySubPlan(p planBuilderSubPlan, plans ... planBuilder) api.Plan + ApplySubPlan(p planBuilderSubPlan, plans ...planBuilder) api.Plan } type withPlanBuilder struct { diff --git a/pkg/deployment/reconcile/plan_builder_encryption.go b/pkg/deployment/reconcile/plan_builder_encryption.go index 2a2283055..e6d468dc3 100644 --- a/pkg/deployment/reconcile/plan_builder_encryption.go +++ b/pkg/deployment/reconcile/plan_builder_encryption.go @@ -55,7 +55,7 @@ func skipEncryptionPlan(spec api.DeploymentSpec, status api.DeploymentStatus) bo func createEncryptionKeyStatusPropagatedFieldUpdate(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, - cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, builders... planBuilder) api.Plan { + cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, builders ...planBuilder) api.Plan { if skipEncryptionPlan(spec, status) { return nil } @@ -76,7 +76,7 @@ func createEncryptionKeyStatusPropagatedFieldUpdate(ctx context.Context, return nil } - if len(plan)==1 && plan[0].Type == api.ActionTypeEncryptionKeyPropagated { + if len(plan) == 1 && plan[0].Type == api.ActionTypeEncryptionKeyPropagated { return plan } diff --git a/pkg/deployment/reconcile/plan_builder_restore.go b/pkg/deployment/reconcile/plan_builder_restore.go index ddc08d7e0..101bd967e 100644 --- a/pkg/deployment/reconcile/plan_builder_restore.go +++ b/pkg/deployment/reconcile/plan_builder_restore.go @@ -114,8 +114,8 @@ func createRestorePlanEncryption(ctx context.Context, log zerolog.Logger, spec a } } - return true,nil + return true, nil } - return true,nil + return true, nil } diff --git a/pkg/deployment/reconcile/plan_builder_tls.go b/pkg/deployment/reconcile/plan_builder_tls.go index a161fda04..54748fc7c 100644 --- a/pkg/deployment/reconcile/plan_builder_tls.go +++ b/pkg/deployment/reconcile/plan_builder_tls.go @@ -45,11 +45,10 @@ import ( const CertificateRenewalMargin = 7 * 24 * time.Hour - func createTLSStatusPropagatedFieldUpdate(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, spec api.DeploymentSpec, status api.DeploymentStatus, - cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, builders... planBuilder) api.Plan { + cachedStatus inspector.Inspector, context PlanBuilderContext, w WithPlanBuilder, builders ...planBuilder) api.Plan { if !spec.TLS.IsSecure() { return nil } diff --git a/pkg/deployment/resources/inspector/inspector.go b/pkg/deployment/resources/inspector/inspector.go index 9f88d54a1..f95482df0 100644 --- a/pkg/deployment/resources/inspector/inspector.go +++ b/pkg/deployment/resources/inspector/inspector.go @@ -117,7 +117,7 @@ type inspector struct { podDisruptionBudgets map[string]*policy.PodDisruptionBudget ns string - k kubernetes.Interface + k kubernetes.Interface } func (i *inspector) Refresh(k kubernetes.Interface, namespace string) error { diff --git a/pkg/deployment/resources/secrets.go b/pkg/deployment/resources/secrets.go index 500b4d60c..edf38267a 100644 --- a/pkg/deployment/resources/secrets.go +++ b/pkg/deployment/resources/secrets.go @@ -27,10 +27,11 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "time" + "github.com/arangodb/kube-arangodb/pkg/deployment/patch" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" - "time" "github.com/arangodb/kube-arangodb/pkg/util" @@ -90,7 +91,7 @@ func (r *Resources) EnsureSecrets(log zerolog.Logger, cachedStatus inspector.Ins if imageFound { if pod.VersionHasJWTSecretKeyfolder(image.ArangoDBVersion, image.Enterprise) { - if err :=r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName)); err != nil { + if err := r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName)); err != nil { return maskAny(err) } } @@ -461,7 +462,6 @@ func (r *Resources) ensureExporterTokenSecret(cachedStatus inspector.Inspector, } } - return operatorErrors.Reconcile() } return nil @@ -629,9 +629,9 @@ func (r *Resources) getSyncMonitoringToken(spec api.DeploymentSpec) (string, err } func getFirstKeyFromMap(m map[string][]byte) (string, []byte, bool) { - for k,v := range m { - return k,v,true + for k, v := range m { + return k, v, true } - return "",nil,false + return "", nil, false } diff --git a/pkg/util/k8sutil/pods.go b/pkg/util/k8sutil/pods.go index 776afd7ce..fc606300e 100644 --- a/pkg/util/k8sutil/pods.go +++ b/pkg/util/k8sutil/pods.go @@ -228,7 +228,7 @@ func ExporterJWTVolumeMount() core.VolumeMount { return core.VolumeMount{ Name: ExporterJWTVolumeName, MountPath: ExporterJWTVolumeMountDir, - ReadOnly:true, + ReadOnly: true, } } From b2cb6f0767f2f0f815d4815c31f9cf6ff75b82cd Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Tue, 14 Jul 2020 15:02:23 +0000 Subject: [PATCH 3/3] Fix tests --- pkg/deployment/reconcile/plan_builder_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/deployment/reconcile/plan_builder_test.go b/pkg/deployment/reconcile/plan_builder_test.go index 8f88619c2..33d021f75 100644 --- a/pkg/deployment/reconcile/plan_builder_test.go +++ b/pkg/deployment/reconcile/plan_builder_test.go @@ -264,6 +264,11 @@ func TestCreatePlanSingleScale(t *testing.T) { // Test with empty status var status api.DeploymentStatus + + status.Hashes.JWT.Propagated = true + status.Hashes.TLS.Propagated = true + status.Hashes.Encryption.Propagated = true + newPlan, changed := createPlan(ctx, log, depl, nil, spec, status, inspector.NewEmptyInspector(), c) assert.True(t, changed) assert.Len(t, newPlan, 0) // Single mode does not scale