Skip to content

Commit 65a6e65

Browse files
authored
Implement token renewal. (#24)
1 parent 438506b commit 65a6e65

File tree

6 files changed

+72
-18
lines changed

6 files changed

+72
-18
lines changed

api/v1/duros_types.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@ type DurosSpec struct {
5555

5656
// DurosStatus defines the observed state of Duros
5757
type DurosStatus struct {
58-
// SecretRef to the create JWT Token
59-
// TODO, this can be used to detect required key rotation
60-
SecretRef string `json:"secret,omitempty" description:"Reference to JWT Token generated on the duros storage side for this project"`
61-
6258
// ManagedResourceStatuses contains a list of statuses of resources managed by this controller
6359
ManagedResourceStatuses []ManagedResourceStatus `json:"managedResourceStatuses" description:"A list of managed resource statuses"`
6460
}

config/crd/bases/storage.metal-stack.io_duros.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,6 @@ spec:
101101
- state
102102
type: object
103103
type: array
104-
secret:
105-
description: SecretRef to the create JWT Token TODO, this can be used
106-
to detect required key rotation
107-
type: string
108104
required:
109105
- managedResourceStatuses
110106
type: object

controllers/duros_controller.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,11 @@ func (r *DurosReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
124124
}
125125
log.Info("created credential", "id", cred.ID, "project", cred.ProjectName)
126126

127-
// Deploy StorageClass Secret
128-
err = r.deployStorageClassSecret(ctx, cred, r.AdminKey)
127+
err = r.reconcileStorageClassSecret(ctx, cred, r.AdminKey)
129128
if err != nil {
130129
return requeue, err
131130
}
132-
// Deploy CSI
131+
133132
err = r.deployCSI(ctx, projectID, storageClasses)
134133
if err != nil {
135134
return requeue, err
@@ -154,8 +153,6 @@ func (r *DurosReconciler) reconcileStatus(ctx context.Context, duros *storagev1.
154153
sts = &appsv1.StatefulSet{}
155154
)
156155

157-
duros.Status.SecretRef = "" // TODO?
158-
159156
err := r.Shoot.Get(ctx, types.NamespacedName{Name: lbCSINodeName, Namespace: namespace}, ds)
160157
if err != nil {
161158
return fmt.Errorf("error getting daemon set: %w", err)

controllers/resources.go

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/go-logr/logr"
10+
"github.com/golang-jwt/jwt/v4"
1011
"github.com/metal-stack/duros-go"
1112
durosv2 "github.com/metal-stack/duros-go/api/duros/v2"
1213

@@ -38,6 +39,9 @@ const (
3839

3940
lbCSIControllerName = "lb-csi-controller"
4041
lbCSINodeName = "lb-csi-node"
42+
43+
tokenLifetime = 8 * 24 * time.Hour
44+
tokenRenewalBefore = 1 * 24 * time.Hour
4145
)
4246

4347
var (
@@ -728,15 +732,68 @@ var (
728732
}
729733
)
730734

731-
func (r *DurosReconciler) deployStorageClassSecret(ctx context.Context, credential *durosv2.Credential, adminKey []byte) error {
735+
func (r *DurosReconciler) reconcileStorageClassSecret(ctx context.Context, credential *durosv2.Credential, adminKey []byte) error {
736+
var (
737+
log = r.Log.WithName("storage-class")
738+
secret = &corev1.Secret{}
739+
)
740+
741+
key := types.NamespacedName{Name: storageClassCredentialsRef, Namespace: namespace}
742+
err := r.Shoot.Get(ctx, key, secret)
743+
if err != nil && apierrors.IsNotFound(err) {
744+
log.Info("deploy storage-class-secret")
745+
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
746+
}
747+
if err != nil {
748+
return fmt.Errorf("unable to read secret: %w", err)
749+
}
750+
751+
// secret already exists, check for renewal
752+
token, ok := secret.Data["jwt"]
753+
if !ok {
754+
log.Error(fmt.Errorf("no storage class token present in existing token"), "recreating storage-class-secret")
755+
err := r.deleteResourceWithWait(ctx, log, deletionResource{
756+
Key: key,
757+
Object: secret,
758+
})
759+
if err != nil {
760+
return err
761+
}
762+
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
763+
}
764+
765+
claims := &jwt.StandardClaims{}
766+
_, _, err = new(jwt.Parser).ParseUnverified(string(token), claims)
767+
if err != nil {
768+
log.Error(err, "storage class token not parsable, recreating storage-class-secret")
769+
err := r.deleteResourceWithWait(ctx, log, deletionResource{
770+
Key: key,
771+
Object: secret,
772+
})
773+
if err != nil {
774+
return err
775+
}
776+
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
777+
}
778+
779+
expiresAt := time.Unix(claims.ExpiresAt, 0)
780+
renewalAt := expiresAt.Add(-tokenRenewalBefore)
781+
if time.Now().After(renewalAt) {
782+
log.Info("storage class token is expiring soon, refreshing token", "expires-at", expiresAt.String())
783+
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
784+
}
785+
786+
log.Info("storage class token is not expiring soon, not doing anything", "expires-at", expiresAt.String(), "renewal-at", renewalAt.String())
787+
788+
return nil
789+
}
790+
791+
func (r *DurosReconciler) deployStorageClassSecret(ctx context.Context, log logr.Logger, credential *durosv2.Credential, adminKey []byte) error {
732792
key, err := extract(adminKey)
733793
if err != nil {
734794
return err
735795
}
736-
log := r.Log.WithName("storage-class")
737-
log.Info("deploy storage-class-secret")
738796

739-
tokenLifetime := 360 * 24 * time.Hour
740797
token, err := duros.NewJWTTokenForCredential(r.Namespace, "duros-controller", credential, []string{credential.ProjectName + ":admin"}, tokenLifetime, key)
741798
if err != nil {
742799
return fmt.Errorf("unable to create jwt token:%w", err)
@@ -754,9 +811,13 @@ func (r *DurosReconciler) deployStorageClassSecret(ctx context.Context, credenti
754811

755812
return nil
756813
})
814+
if err != nil {
815+
return err
816+
}
817+
757818
log.Info("storageclasssecret", "name", storageClassCredentialsRef, "operation", op)
758819

759-
return err
820+
return nil
760821
}
761822

762823
func (r *DurosReconciler) deployCSI(ctx context.Context, projectID string, scs []storagev1.StorageClass) error {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
cloud.google.com/go v0.94.1 // indirect
77
github.com/go-logr/logr v0.4.0
88
github.com/go-logr/zapr v0.4.0
9+
github.com/golang-jwt/jwt/v4 v4.0.0
910
github.com/google/gofuzz v1.2.0 // indirect
1011
github.com/metal-stack/duros-go v0.2.3
1112
github.com/metal-stack/v v1.0.3

main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os"
2323

2424
"go.uber.org/zap"
25+
"go.uber.org/zap/zapcore"
2526
"k8s.io/client-go/tools/clientcmd"
2627

2728
"github.com/go-logr/zapr"
@@ -97,6 +98,8 @@ func main() {
9798

9899
cfg := zap.NewProductionConfig()
99100
cfg.Level = zap.NewAtomicLevelAt(level)
101+
cfg.EncoderConfig.TimeKey = "timestamp"
102+
cfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
100103

101104
l, err := cfg.Build()
102105
if err != nil {

0 commit comments

Comments
 (0)