Skip to content

Commit

Permalink
Merge pull request #275 from gianlucam76/should-reconcile
Browse files Browse the repository at this point in the history
(chore) renew token 10 minutes before expected renewal time
  • Loading branch information
gianlucam76 authored Feb 9, 2025
2 parents ccaa785 + 4776463 commit c94c88d
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
5 changes: 4 additions & 1 deletion controllers/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.
package controllers

var (
ShouldRenewTokenRequest = (*SveltosClusterReconciler).shouldRenewTokenRequest
ShouldRenewTokenRequest = (*SveltosClusterReconciler).shouldRenewTokenRequest
GetTokenExpiration = (*SveltosClusterReconciler).getTokenExpiration
AdjustTokenRequestRenewalOption = (*SveltosClusterReconciler).adjustTokenRequestRenewalOption
GetServiceAccountTokenRequest = (*SveltosClusterReconciler).getServiceAccountTokenRequest
)

var (
Expand Down
74 changes: 73 additions & 1 deletion controllers/sveltoscluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package controllers
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/Masterminds/semver"
Expand Down Expand Up @@ -286,9 +288,19 @@ func (r *SveltosClusterReconciler) shouldRenewTokenRequest(sveltosClusterScope *
}
}

renewalInterval := sveltosCluster.Spec.TokenRequestRenewalOption.RenewTokenRequestInterval.Seconds()
// Calculate the time 10 minutes before the renewal interval
const tenMinutes = 10 * 60
var renewalThreshold time.Duration
if renewalInterval > tenMinutes {
renewalThreshold = time.Duration(renewalInterval-tenMinutes) * time.Second
} else {
renewalThreshold = time.Duration(renewalInterval) * time.Second
}

// Calculate how much time has passed since lastRenewal
elapsed := currentTime.Sub(lastRenewal.Time)
return elapsed.Seconds() > sveltosCluster.Spec.TokenRequestRenewalOption.RenewTokenRequestInterval.Seconds()
return elapsed.Seconds() > renewalThreshold.Seconds()
}

func (r *SveltosClusterReconciler) handleTokenRequestRenewal(ctx context.Context,
Expand Down Expand Up @@ -344,6 +356,9 @@ func (r *SveltosClusterReconciler) handleTokenRequestRenewal(ctx context.Context
continue
}

sveltosCluster.Spec.TokenRequestRenewalOption.RenewTokenRequestInterval =
r.adjustTokenRequestRenewalOption(sveltosCluster.Spec.TokenRequestRenewalOption, tokenRequest, logger)

logger.V(logs.LogDebug).Info("Get Kubeconfig from TokenRequest")
key := "re-kubeconfig"
data := r.getKubeconfigFromToken(saNamespace, saName, tokenRequest.Token, remoteConfig)
Expand Down Expand Up @@ -394,6 +409,33 @@ func (r *SveltosClusterReconciler) getServiceAccountTokenRequest(ctx context.Con
return &tokenRequest.Status, nil
}

func (r *SveltosClusterReconciler) getTokenExpiration(jwt string) (time.Time, error) {
parts := strings.Split(jwt, ".")
//nolint: mnd // expected format
if len(parts) != 3 {
return time.Time{}, fmt.Errorf("invalid JWT format")
}

payloadBase64 := parts[1]
payloadBytes, err := base64.RawURLEncoding.DecodeString(payloadBase64)
if err != nil {
return time.Time{}, fmt.Errorf("failed to decode payload: %w", err)
}

var payload map[string]interface{}
if err := json.Unmarshal(payloadBytes, &payload); err != nil {
return time.Time{}, fmt.Errorf("failed to unmarshal payload: %w", err)
}

exp, ok := payload["exp"].(float64)
if !ok {
return time.Time{}, fmt.Errorf("expiration time not found in payload")
}

expirationTime := time.Unix(int64(exp), 0)
return expirationTime, nil
}

// getKubeconfigFromToken returns Kubeconfig to access management cluster from token.
func (r *SveltosClusterReconciler) getKubeconfigFromToken(namespace, serviceAccountName, token string,
remoteConfig *rest.Config) string {
Expand Down Expand Up @@ -505,3 +547,33 @@ func getNextScheduleTime(schedule string, lastRunTime *metav1.Time, now time.Tim
next := sched.Next(now)
return &next, nil
}

func (r *SveltosClusterReconciler) adjustTokenRequestRenewalOption(
tokenRequestRenewalOption *libsveltosv1beta1.TokenRequestRenewalOption,
tokenRequest *authenticationv1.TokenRequestStatus, logger logr.Logger) metav1.Duration {

jwt := tokenRequest.Token

expirationTime, err := r.getTokenExpiration(jwt)
if err != nil {
logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get token expiration: %v", err))
return tokenRequestRenewalOption.RenewTokenRequestInterval
}

logger.V(logs.LogDebug).Info(fmt.Sprintf("token expires at: %v\n", expirationTime))

saExpirationInSecond := tokenRequestRenewalOption.RenewTokenRequestInterval.Duration.Seconds()
expectedExpiration := time.Now().Add(time.Duration(saExpirationInSecond) * time.Second)

if expirationTime.Before(expectedExpiration) {
diff := expectedExpiration.Sub(expirationTime)
logger.V(logs.LogInfo).Info(
fmt.Sprintf("Token expiration is shorter than expected by %v. Requested: %v, Actual: %v",
diff, expectedExpiration, expirationTime))

actualLifetime := time.Until(expirationTime)
return metav1.Duration{Duration: actualLifetime}
}

return tokenRequestRenewalOption.RenewTokenRequestInterval
}
53 changes: 53 additions & 0 deletions controllers/sveltoscluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,59 @@ var _ = Describe("SveltosCluster: Reconciler", func() {
Expect(sveltosCluster.Status.NextPause.Time).To(Equal(expectedPause))
Expect(sveltosCluster.Status.NextUnpause.Time).To(Equal(expectedUnpause))
})

It("getTokenExpiration returns token expiration time", func() {
initObjects := []client.Object{
sveltosCluster,
}

c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...).WithObjects(initObjects...).Build()

reconciler := getClusterProfileReconciler(c)

//nolint: gosec,lll // this is an already expired token
const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InpuMW5mc25rdFZRLWM2UlF2Sjk3OHFXZS10LWIwSVpHVHphYjhfZHNPaE0ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzM5MTcyNDI0LCJpYXQiOjE3Mzg4MjY4MjQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYzdhMGUwZGItNDZiYS00OTdlLTg5NmYtZDU1NWY0NmM2YmJmIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6InBsYXRmb3JtIiwidWlkIjoiMzVjOTc5NjgtMmNiNi00YWE1LWFlMzMtZDE2YjEzNWRhMjg1In19LCJuYmYiOjE3Mzg4MjY4MjQsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnBsYXRmb3JtIn0.eYTP3rPp2gG8p91sxh7E6mS6GlizBJOO4fPDR5G95VzOrdZFZflxv_pika-Z34Ut3qtggtZejxytZdZkSMv_fq9sYFJG57vqC48u1oUa_7nD6ej_37ojW-y-VZ5IFgkws-_37VNzsdSFaQPU4ybHaDVkyXrSnSYs90zRT40LY-Ee1Ro__-UDEPxuQS-qPKOwGb-V590PP4td9LUq8Wkh1SwKv7XZUlSACqTxN3OYYVoqcBYeqob6QtkTgxNZMlY4DQZBTlCSQCpo2GKnASxDfLWFX6yivchD2HpJpIe1JnDs6R17u6iXidrnobyYWWEf_DAyAkoEq9HoivbYCs5IBQ"

expirationTime, err := controllers.GetTokenExpiration(reconciler, token)
Expect(err).To(BeNil())

cetLocation, err := time.LoadLocation("Europe/Berlin") // Berlin is in CET
Expect(err).To(BeNil())
cetTime := expirationTime.In(cetLocation)

Expect(cetTime.String()).To(Equal("2025-02-10 08:27:04 +0100 CET"))
})

It("adjustTokenRequestRenewalOption returns correct time when renew is due", func() {
reconciler := getClusterProfileReconciler(testEnv.Client)

sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: randomString(),
Namespace: "default",
},
}

Expect(testEnv.Create(context.TODO(), sa)).To(Succeed())
Expect(waitForObject(context.TODO(), testEnv.Client, sa)).To(Succeed())

// Create a token with an expiration time of one day
const oneDayInSeconds = 24 * 60 * 60
tokenRequestStatus, err := controllers.GetServiceAccountTokenRequest(reconciler, context.TODO(), testEnv.Config,
sa.Namespace, sa.Name, oneDayInSeconds, logger)
Expect(err).To(BeNil())

const twoDaysInSecond = 2 * oneDayInSeconds
tokenRequestRenewalOption := &libsveltosv1beta1.TokenRequestRenewalOption{
RenewTokenRequestInterval: metav1.Duration{Duration: twoDaysInSecond * time.Second},
}

// Token had an expiration of 24 hours (one day)
// SveltosCluster TokenRequestRenewalOption was set to request a renawal every 48 hours (two days)
// AdjustTokenRequestRenewalOption will return a token that is set to less than one day
newDuration := controllers.AdjustTokenRequestRenewalOption(reconciler, tokenRequestRenewalOption, tokenRequestStatus, logger)
Expect(newDuration.Duration < oneDayInSeconds*time.Second).To(BeTrue())
})
})

func getSveltosClusterInstance(namespace, name string) *libsveltosv1beta1.SveltosCluster {
Expand Down

0 comments on commit c94c88d

Please sign in to comment.