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"
2022-02-16 13:29:24 +00:00
"github.com/arangodb/kube-arangodb/pkg/deployment/actions"
2022-07-11 11:49:47 +00:00
"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"
2022-07-11 11:49:47 +00:00
"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-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 ,
2022-05-15 16:11:41 +00:00
context PlanBuilderContext ) api . Plan {
2020-06-26 06:53:24 +00:00
if folder , err := ensureJWTFolderSupport ( spec , status ) ; err != nil || ! folder {
return nil
}
2022-05-15 16:11:41 +00:00
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
}
2022-05-15 16:11:41 +00:00
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
}
2020-07-03 14:27:05 +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 ) )
2020-07-03 14:27:05 +00:00
}
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 {
2020-07-03 14:27:05 +00:00
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 ,
2022-05-15 16:11:41 +00:00
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 ,
2022-05-15 16:11:41 +00:00
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
}
2022-05-15 16:11:41 +00:00
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
}
2022-05-15 16:11:41 +00:00
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 {
2020-07-03 14:27:05 +00:00
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 )
keys = util . PrefixStringArray ( keys , "sha256:" )
2021-05-18 14:08:16 +00:00
return ! util . 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
2022-07-24 18:26:26 +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
}
2022-07-24 18:26:26 +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
2022-07-14 09:22:50 +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 ( ) ) , 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 {
2022-02-16 13:29:24 +00:00
got := len ( acts ) != 0
2020-06-26 06:53:24 +00:00
cond := conditionFalse
if ! got {
cond = conditionTrue
}
if s . Hashes . JWT . Propagated == got {
2022-02-16 13:29:24 +00:00
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
}
2022-02-16 13:29: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 {
2020-07-03 14:27:05 +00:00
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
}