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_jwt.go

315 lines
8.8 KiB
Go
Raw Normal View History

2020-06-26 06:53:24 +00:00
//
// DISCLAIMER
//
2022-01-10 11:35:49 +00:00
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
2020-06-26 06:53:24 +00:00
//
// 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"
"fmt"
"sort"
2021-02-10 08:17:52 +00:00
"time"
2020-06-26 06:53:24 +00:00
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"
2020-06-26 06:53:24 +00:00
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
2020-06-26 06:53:24 +00:00
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
2022-12-22 09:49:51 +00:00
"github.com/arangodb/kube-arangodb/pkg/util/strings"
2020-06-26 06:53:24 +00:00
)
2022-06-14 07:26:07 +00:00
func (r *Reconciler) createJWTKeyUpdate(ctx context.Context, apiObject k8sutil.APIObject,
2020-06-26 06:53:24 +00:00
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) api.Plan {
2020-06-26 06:53:24 +00:00
if folder, err := ensureJWTFolderSupport(spec, status); err != nil || !folder {
return nil
}
folder, ok := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(pod.JWTSecretFolder(apiObject.GetName()))
2020-06-26 06:53:24 +00:00
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Error("Unable to get JWT folder info")
2020-06-26 06:53:24 +00:00
return nil
}
s, ok := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(spec.Authentication.GetJWTSecretName())
2020-06-26 06:53:24 +00:00
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Info("JWT Secret is missing, no rotation will take place")
2020-06-26 06:53:24 +00:00
return nil
}
jwt, ok := s.Data[constants.SecretKeyToken]
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Warn("JWT Secret is invalid, no rotation will take place")
return r.addJWTPropagatedPlanAction(status)
2020-06-26 06:53:24 +00:00
}
jwtSha := util.SHA256(jwt)
if _, ok := folder.Data[jwtSha]; !ok {
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTAdd, "Add JWTRotation key").AddParam(checksum, jwtSha))
2020-06-26 06:53:24 +00:00
}
activeKey, ok := folder.Data[pod.ActiveJWTKey]
if !ok {
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTSetActive, "Set active key").AddParam(checksum, jwtSha))
2020-06-26 06:53:24 +00:00
}
tokenKey, ok := folder.Data[constants.SecretKeyToken]
if !ok || util.SHA256(activeKey) != util.SHA256(tokenKey) {
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTSetActive, "Set active key and add token field").AddParam(checksum, jwtSha))
}
2022-06-14 07:26:07 +00:00
plan, failed := r.areJWTTokensUpToDate(ctx, status, context, folder)
2020-06-26 06:53:24 +00:00
if len(plan) > 0 {
return plan
}
if failed {
2022-06-14 07:26:07 +00:00
r.planLogger.Info("JWT Failed on one pod, no rotation will take place")
2020-06-26 06:53:24 +00:00
return nil
}
if util.SHA256(activeKey) != jwtSha {
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTSetActive, "Set active key").AddParam(checksum, jwtSha))
2020-06-26 06:53:24 +00:00
}
for key := range folder.Data {
if key == pod.ActiveJWTKey || key == constants.SecretKeyToken {
2020-06-26 06:53:24 +00:00
continue
}
if key == jwtSha {
continue
}
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTClean, "Remove old key").AddParam(checksum, key))
2020-06-26 06:53:24 +00:00
}
2022-06-14 07:26:07 +00:00
return r.addJWTPropagatedPlanAction(status)
2020-06-26 06:53:24 +00:00
}
2022-06-14 07:26:07 +00:00
func (r *Reconciler) createJWTStatusUpdate(ctx context.Context, apiObject k8sutil.APIObject,
2020-06-26 06:53:24 +00:00
spec api.DeploymentSpec, status api.DeploymentStatus,
context PlanBuilderContext) api.Plan {
2020-06-26 06:53:24 +00:00
if _, err := ensureJWTFolderSupport(spec, status); err != nil {
return nil
}
2022-06-14 07:26:07 +00:00
if r.createJWTStatusUpdateRequired(apiObject, spec, status, context) {
return r.addJWTPropagatedPlanAction(status, actions.NewClusterAction(api.ActionTypeJWTStatusUpdate, "Update status"))
2020-06-26 06:53:24 +00:00
}
return nil
}
2022-06-14 07:26:07 +00:00
func (r *Reconciler) createJWTStatusUpdateRequired(apiObject k8sutil.APIObject, spec api.DeploymentSpec,
status api.DeploymentStatus, context PlanBuilderContext) bool {
2020-06-26 06:53:24 +00:00
folder, err := ensureJWTFolderSupport(spec, status)
if err != nil {
2022-06-14 07:26:07 +00:00
r.planLogger.Err(err).Error("Action not supported")
2020-06-26 06:53:24 +00:00
return false
}
if !folder {
if status.Hashes.JWT.Passive != nil {
return true
}
f, ok := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(spec.Authentication.GetJWTSecretName())
2020-06-26 06:53:24 +00:00
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Error("Unable to get JWT secret info")
2020-06-26 06:53:24 +00:00
return false
}
key, ok := f.Data[constants.SecretKeyToken]
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Error("JWT Token is invalid")
2020-06-26 06:53:24 +00:00
return false
}
keySha := fmt.Sprintf("sha256:%s", util.SHA256(key))
if status.Hashes.JWT.Active != keySha {
2022-06-14 07:26:07 +00:00
r.planLogger.Error("JWT Token is invalid")
2020-06-26 06:53:24 +00:00
return true
}
return false
}
f, ok := context.ACS().CurrentClusterCache().Secret().V1().GetSimple(pod.JWTSecretFolder(apiObject.GetName()))
2020-06-26 06:53:24 +00:00
if !ok {
2022-06-14 07:26:07 +00:00
r.planLogger.Error("Unable to get JWT folder info")
2020-06-26 06:53:24 +00:00
return false
}
activeKeyData, active := f.Data[pod.ActiveJWTKey]
activeKeyShort := util.SHA256(activeKeyData)
activeKey := fmt.Sprintf("sha256:%s", activeKeyShort)
if active {
if status.Hashes.JWT.Active != activeKey {
return true
}
}
if len(f.Data) == 0 {
2021-05-18 14:08:16 +00:00
return status.Hashes.JWT.Passive != nil
2020-06-26 06:53:24 +00:00
}
var keys []string
for key := range f.Data {
if key == pod.ActiveJWTKey || key == activeKeyShort || key == constants.SecretKeyToken {
2020-06-26 06:53:24 +00:00
continue
}
keys = append(keys, key)
}
if len(keys) == 0 {
2021-05-18 14:08:16 +00:00
return status.Hashes.JWT.Passive != nil
2020-06-26 06:53:24 +00:00
}
sort.Strings(keys)
2022-12-22 09:49:51 +00:00
keys = strings.PrefixStringArray(keys, "sha256:")
2020-06-26 06:53:24 +00:00
2022-12-22 09:49:51 +00:00
return !strings.CompareStringArray(keys, status.Hashes.JWT.Passive)
2020-06-26 06:53:24 +00:00
}
2022-06-14 07:26:07 +00:00
func (r *Reconciler) areJWTTokensUpToDate(ctx context.Context, status api.DeploymentStatus,
2021-05-07 14:13:15 +00:00
planCtx PlanBuilderContext, folder *core.Secret) (plan api.Plan, failed bool) {
2021-02-10 08:17:52 +00:00
gCtx, c := context.WithTimeout(ctx, 2*time.Second)
defer c()
2020-06-26 06:53:24 +00:00
for _, e := range status.Members.AsList() {
nCtx, c := context.WithTimeout(gCtx, 500*time.Millisecond)
defer c()
if updateRequired, failedMember := r.isJWTTokenUpToDate(nCtx, status, planCtx, e.Group, e.Member, folder); failedMember {
failed = true
continue
} else if updateRequired {
plan = append(plan, actions.NewAction(api.ActionTypeJWTRefresh, e.Group, e.Member))
continue
2020-06-26 06:53:24 +00:00
}
}
2020-06-26 06:53:24 +00:00
return
}
2022-06-14 07:26:07 +00:00
func (r *Reconciler) isJWTTokenUpToDate(ctx context.Context, status api.DeploymentStatus, context PlanBuilderContext,
2021-05-07 14:13:15 +00:00
group api.ServerGroup, m api.MemberStatus, folder *core.Secret) (updateRequired bool, failed bool) {
2020-06-26 06:53:24 +00:00
if m.Phase != api.MemberPhaseCreated {
return false, true
}
2020-07-21 07:32:02 +00:00
if i, ok := status.Images.GetByImageID(m.ImageID); !ok || !features.JWTRotation().Supported(i.ArangoDBVersion, i.Enterprise) {
2020-06-26 06:53:24 +00:00
return false, false
}
2022-06-14 07:26:07 +00:00
log := r.planLogger.Str("group", group.AsRole()).Str("member", m.ID)
2020-06-26 06:53:24 +00:00
c, err := context.GetMembersState().GetMemberClient(m.ID)
2020-06-26 06:53:24 +00:00
if err != nil {
2022-06-14 07:26:07 +00:00
log.Err(err).Warn("Unable to get client")
2020-06-26 06:53:24 +00:00
return false, true
}
if updateRequired, err := isMemberJWTTokenInvalid(ctx, client.NewClient(c.Connection(), log), folder.Data, false); err != nil {
2022-06-14 07:26:07 +00:00
log.Err(err).Warn("JWT UpToDate Check failed")
2020-06-26 06:53:24 +00:00
return false, true
} else if updateRequired {
return true, false
}
return false, false
}
2022-06-14 07:26:07 +00:00
func (r *Reconciler) addJWTPropagatedPlanAction(s api.DeploymentStatus, acts ...api.Action) api.Plan {
got := len(acts) != 0
2020-06-26 06:53:24 +00:00
cond := conditionFalse
if !got {
cond = conditionTrue
}
if s.Hashes.JWT.Propagated == got {
p := api.Plan{actions.NewClusterAction(api.ActionTypeJWTPropagated, "Change propagated flag").AddParam(propagated, cond)}
return append(p, acts...)
2020-06-26 06:53:24 +00:00
}
return acts
2020-06-26 06:53:24 +00:00
}
func isMemberJWTTokenInvalid(ctx context.Context, c client.Client, data map[string][]byte, refresh bool) (bool, error) {
cmd := c.GetJWT
if refresh {
cmd = c.RefreshJWT
}
e, err := cmd(ctx)
if err != nil {
return false, errors.Wrapf(err, "Unable to fetch JWT tokens")
}
if e.Result.Active == nil {
return false, errors.Wrapf(err, "There is no active JWT Token")
}
if jwtActive, ok := data[pod.ActiveJWTKey]; !ok {
2021-01-08 14:35:38 +00:00
return false, errors.Newf("Missing Active JWT Token in folder")
2020-06-26 06:53:24 +00:00
} else if util.SHA256(jwtActive) != e.Result.Active.GetSHA().Checksum() {
return true, nil
}
if !compareJWTKeys(e.Result.Passive, data) {
return true, nil
}
return false, nil
}
func compareJWTKeys(e client.Entries, keys map[string][]byte) bool {
for k := range keys {
if k == pod.ActiveJWTKey || k == constants.SecretKeyToken {
2020-06-26 06:53:24 +00:00
continue
}
if !e.Contains(k) {
return false
}
}
for _, entry := range e {
if entry.GetSHA() == "" {
continue
}
if _, ok := keys[entry.GetSHA().Checksum()]; !ok {
return false
}
}
return true
}