Skip to content

Commit

Permalink
Implement token renewal. (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit91 authored Sep 16, 2021
1 parent 438506b commit 65a6e65
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 18 deletions.
4 changes: 0 additions & 4 deletions api/v1/duros_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ type DurosSpec struct {

// DurosStatus defines the observed state of Duros
type DurosStatus struct {
// SecretRef to the create JWT Token
// TODO, this can be used to detect required key rotation
SecretRef string `json:"secret,omitempty" description:"Reference to JWT Token generated on the duros storage side for this project"`

// ManagedResourceStatuses contains a list of statuses of resources managed by this controller
ManagedResourceStatuses []ManagedResourceStatus `json:"managedResourceStatuses" description:"A list of managed resource statuses"`
}
Expand Down
4 changes: 0 additions & 4 deletions config/crd/bases/storage.metal-stack.io_duros.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ spec:
- state
type: object
type: array
secret:
description: SecretRef to the create JWT Token TODO, this can be used
to detect required key rotation
type: string
required:
- managedResourceStatuses
type: object
Expand Down
7 changes: 2 additions & 5 deletions controllers/duros_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,11 @@ func (r *DurosReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}
log.Info("created credential", "id", cred.ID, "project", cred.ProjectName)

// Deploy StorageClass Secret
err = r.deployStorageClassSecret(ctx, cred, r.AdminKey)
err = r.reconcileStorageClassSecret(ctx, cred, r.AdminKey)
if err != nil {
return requeue, err
}
// Deploy CSI

err = r.deployCSI(ctx, projectID, storageClasses)
if err != nil {
return requeue, err
Expand All @@ -154,8 +153,6 @@ func (r *DurosReconciler) reconcileStatus(ctx context.Context, duros *storagev1.
sts = &appsv1.StatefulSet{}
)

duros.Status.SecretRef = "" // TODO?

err := r.Shoot.Get(ctx, types.NamespacedName{Name: lbCSINodeName, Namespace: namespace}, ds)
if err != nil {
return fmt.Errorf("error getting daemon set: %w", err)
Expand Down
71 changes: 66 additions & 5 deletions controllers/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/go-logr/logr"
"github.com/golang-jwt/jwt/v4"
"github.com/metal-stack/duros-go"
durosv2 "github.com/metal-stack/duros-go/api/duros/v2"

Expand Down Expand Up @@ -38,6 +39,9 @@ const (

lbCSIControllerName = "lb-csi-controller"
lbCSINodeName = "lb-csi-node"

tokenLifetime = 8 * 24 * time.Hour
tokenRenewalBefore = 1 * 24 * time.Hour
)

var (
Expand Down Expand Up @@ -728,15 +732,68 @@ var (
}
)

func (r *DurosReconciler) deployStorageClassSecret(ctx context.Context, credential *durosv2.Credential, adminKey []byte) error {
func (r *DurosReconciler) reconcileStorageClassSecret(ctx context.Context, credential *durosv2.Credential, adminKey []byte) error {
var (
log = r.Log.WithName("storage-class")
secret = &corev1.Secret{}
)

key := types.NamespacedName{Name: storageClassCredentialsRef, Namespace: namespace}
err := r.Shoot.Get(ctx, key, secret)
if err != nil && apierrors.IsNotFound(err) {
log.Info("deploy storage-class-secret")
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
}
if err != nil {
return fmt.Errorf("unable to read secret: %w", err)
}

// secret already exists, check for renewal
token, ok := secret.Data["jwt"]
if !ok {
log.Error(fmt.Errorf("no storage class token present in existing token"), "recreating storage-class-secret")
err := r.deleteResourceWithWait(ctx, log, deletionResource{
Key: key,
Object: secret,
})
if err != nil {
return err
}
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
}

claims := &jwt.StandardClaims{}
_, _, err = new(jwt.Parser).ParseUnverified(string(token), claims)
if err != nil {
log.Error(err, "storage class token not parsable, recreating storage-class-secret")
err := r.deleteResourceWithWait(ctx, log, deletionResource{
Key: key,
Object: secret,
})
if err != nil {
return err
}
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
}

expiresAt := time.Unix(claims.ExpiresAt, 0)
renewalAt := expiresAt.Add(-tokenRenewalBefore)
if time.Now().After(renewalAt) {
log.Info("storage class token is expiring soon, refreshing token", "expires-at", expiresAt.String())
return r.deployStorageClassSecret(ctx, log, credential, adminKey)
}

log.Info("storage class token is not expiring soon, not doing anything", "expires-at", expiresAt.String(), "renewal-at", renewalAt.String())

return nil
}

func (r *DurosReconciler) deployStorageClassSecret(ctx context.Context, log logr.Logger, credential *durosv2.Credential, adminKey []byte) error {
key, err := extract(adminKey)
if err != nil {
return err
}
log := r.Log.WithName("storage-class")
log.Info("deploy storage-class-secret")

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

return nil
})
if err != nil {
return err
}

log.Info("storageclasssecret", "name", storageClassCredentialsRef, "operation", op)

return err
return nil
}

func (r *DurosReconciler) deployCSI(ctx context.Context, projectID string, scs []storagev1.StorageClass) error {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
cloud.google.com/go v0.94.1 // indirect
github.com/go-logr/logr v0.4.0
github.com/go-logr/zapr v0.4.0
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/google/gofuzz v1.2.0 // indirect
github.com/metal-stack/duros-go v0.2.3
github.com/metal-stack/v v1.0.3
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"k8s.io/client-go/tools/clientcmd"

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

cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(level)
cfg.EncoderConfig.TimeKey = "timestamp"
cfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder

l, err := cfg.Build()
if err != nil {
Expand Down

0 comments on commit 65a6e65

Please sign in to comment.