Skip to content

Commit b5b2802

Browse files
tvoranjaireddjawed
andauthored
VSS: Add vault client callback handler (#867)
* VSS: add client callback to reconcile when reauth needed * Adding VaultClientMeta And sync reasons similar to VaultDynamicSecret * Update vaultstaticsecret_controller.go * Added integration test that confirms that no EOF error occurs after 3 ttl cycles * comment description --------- Co-authored-by: Jaired Jawed <jaired.jawed@hashicorp.com>
1 parent 3795eab commit b5b2802

File tree

7 files changed

+350
-9
lines changed

7 files changed

+350
-9
lines changed

api/v1beta1/vaultstaticsecret_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ type VaultStaticSecretStatus struct {
8585
// The SecretMac is also used to detect drift in the Destination Secret's Data.
8686
// If drift is detected the data will be synced to the Destination.
8787
SecretMAC string `json:"secretMAC,omitempty"`
88+
// VaultClientMeta contains the status of the Vault client and is used during
89+
// resource reconciliation.
90+
VaultClientMeta VaultClientMeta `json:"vaultClientMeta,omitempty"`
8891
// Conditions hold information that can be used by other apps to determine the
8992
// health of the resource instance.
9093
Conditions []metav1.Condition `json:"conditions,omitempty"`

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chart/crds/secrets.hashicorp.com_vaultstaticsecrets.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,21 @@ spec:
379379
The SecretMac is also used to detect drift in the Destination Secret's Data.
380380
If drift is detected the data will be synced to the Destination.
381381
type: string
382+
vaultClientMeta:
383+
description: |-
384+
VaultClientMeta contains the status of the Vault client and is used during
385+
resource reconciliation.
386+
properties:
387+
cacheKey:
388+
description: CacheKey is the unique key used to identify the client
389+
cache.
390+
type: string
391+
id:
392+
description: |-
393+
ID is the Vault ID of the authenticated client. The ID should never contain
394+
any sensitive information.
395+
type: string
396+
type: object
382397
required:
383398
- lastGeneration
384399
type: object

config/crd/bases/secrets.hashicorp.com_vaultstaticsecrets.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,21 @@ spec:
379379
The SecretMac is also used to detect drift in the Destination Secret's Data.
380380
If drift is detected the data will be synced to the Destination.
381381
type: string
382+
vaultClientMeta:
383+
description: |-
384+
VaultClientMeta contains the status of the Vault client and is used during
385+
resource reconciliation.
386+
properties:
387+
cacheKey:
388+
description: CacheKey is the unique key used to identify the client
389+
cache.
390+
type: string
391+
id:
392+
description: |-
393+
ID is the Vault ID of the authenticated client. The ID should never contain
394+
any sensitive information.
395+
type: string
396+
type: object
382397
required:
383398
- lastGeneration
384399
type: object

controllers/vaultstaticsecret_controller.go

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ func (r *VaultStaticSecretReconciler) Reconcile(ctx context.Context, req ctrl.Re
107107
}, nil
108108
}
109109

110+
destExists, _ := helpers.CheckSecretExists(ctx, r.Client, o)
111+
if !o.Spec.Destination.Create && !destExists {
112+
logger.Info("Destination secret does not exist, either create it or "+
113+
"set .spec.destination.create=true", "destination", o.Spec.Destination)
114+
return ctrl.Result{RequeueAfter: requeueDurationOnError}, nil
115+
}
116+
117+
// we can ignore the error here, since it was handled above in the Get() call.
118+
clientCacheKey, _ := c.GetCacheKey()
119+
120+
// update the VaultClientMeta in the resource's status.
121+
o.Status.VaultClientMeta.CacheKey = clientCacheKey.String()
122+
o.Status.VaultClientMeta.ID = c.ID()
123+
110124
var requeueAfter time.Duration
111125
if o.Spec.RefreshAfter != "" {
112126
d, err := parseDurationString(o.Spec.RefreshAfter, ".spec.refreshAfter", 0)
@@ -240,22 +254,15 @@ func (r *VaultStaticSecretReconciler) Reconcile(ctx context.Context, req ctrl.Re
240254
if err := helpers.SyncSecret(ctx, r.Client, o, data); err != nil {
241255
r.Recorder.Eventf(o, corev1.EventTypeWarning, consts.ReasonSecretSyncError,
242256
"Failed to update k8s secret: %s", err)
243-
244-
horizon := computeHorizonWithJitter(requeueDurationOnError)
245-
if err := r.updateStatus(ctx, o, false, newSyncCondition(o, metav1.ConditionFalse, "Failed to sync the secret, horizon=%s, err=%s", horizon, err)); err != nil {
246-
return ctrl.Result{}, err
247-
}
248-
249-
return ctrl.Result{
250-
RequeueAfter: horizon,
251-
}, nil
257+
return ctrl.Result{RequeueAfter: computeHorizonWithJitter(requeueDurationOnError)}, nil
252258
}
253259

254260
conditions = append(conditions,
255261
newSyncCondition(o, metav1.ConditionTrue,
256262
"Secret synced, horizon=%s", requeueAfter),
257263
)
258264
r.Recorder.Event(o, corev1.EventTypeNormal, reason, "Secret synced")
265+
259266
if doRolloutRestart && len(o.Spec.RolloutRestartTargets) > 0 {
260267
reason = consts.ReasonSecretRotated
261268
// rollout-restart errors are not retryable
@@ -462,6 +469,8 @@ eventLoop:
462469
r.Recorder.Eventf(&o, corev1.EventTypeWarning, consts.ReasonEventWatcherError,
463470
"Error while watching events: %s", err)
464471

472+
logger.Error(err, "Error while watching events", "namespace", o.Namespace, "name", o.Name)
473+
465474
if errorCount >= errorThreshold {
466475
logger.Error(err, "Too many errors while watching events, requeuing")
467476
break eventLoop
@@ -596,6 +605,14 @@ func (r *VaultStaticSecretReconciler) SetupWithManager(mgr ctrl.Manager, opts co
596605
if r.BackOffRegistry == nil {
597606
r.BackOffRegistry = NewBackOffRegistry()
598607
}
608+
609+
r.ClientFactory.RegisterClientCallbackHandler(
610+
vault.ClientCallbackHandler{
611+
On: vault.ClientCallbackOnLifetimeWatcherDone | vault.ClientCallbackOnCacheRemoval,
612+
Callback: r.vaultClientCallback,
613+
},
614+
)
615+
599616
r.SourceCh = make(chan event.GenericEvent)
600617
r.eventWatcherRegistry = newEventWatcherRegistry()
601618

@@ -629,6 +646,67 @@ func (r *VaultStaticSecretReconciler) SetupWithManager(mgr ctrl.Manager, opts co
629646
Complete(r)
630647
}
631648

649+
// vaultClientCallback requests reconciliation of all VaultStaticSecret
650+
// instances that were synced with Client
651+
func (r *VaultStaticSecretReconciler) vaultClientCallback(ctx context.Context, c vault.Client) {
652+
logger := log.FromContext(ctx).WithName("vaultClientCallback")
653+
654+
cacheKey, err := c.GetCacheKey()
655+
if err != nil {
656+
// should never get here
657+
logger.Error(err, "Invalid cache key, skipping syncing of VaultStaticSecret instances")
658+
return
659+
}
660+
661+
logger = logger.WithValues("cacheKey", cacheKey, "controller", "vss")
662+
var l secretsv1beta1.VaultStaticSecretList
663+
if err := r.Client.List(ctx, &l, client.InNamespace(
664+
c.GetCredentialProvider().GetNamespace()),
665+
); err != nil {
666+
logger.Error(err, "Failed to list VaultStaticSecret instances")
667+
return
668+
}
669+
670+
if len(l.Items) == 0 {
671+
return
672+
}
673+
674+
reqs := map[client.ObjectKey]empty{}
675+
for _, o := range l.Items {
676+
if o.Status.VaultClientMeta.CacheKey == "" {
677+
logger.V(consts.LogLevelWarning).Info("Skipping, cacheKey is empty",
678+
"object", client.ObjectKeyFromObject(&o))
679+
continue
680+
}
681+
682+
curCacheKey := vault.ClientCacheKey(o.Status.VaultClientMeta.CacheKey)
683+
if ok, err := curCacheKey.SameParent(cacheKey); ok {
684+
evt := event.GenericEvent{
685+
Object: &secretsv1beta1.VaultStaticSecret{
686+
ObjectMeta: metav1.ObjectMeta{
687+
Namespace: o.GetNamespace(),
688+
Name: o.GetName(),
689+
},
690+
},
691+
}
692+
693+
objKey := client.ObjectKeyFromObject(evt.Object)
694+
if _, ok := reqs[objKey]; !ok {
695+
// deduplicating is probably not necessary, but we do it just in case.
696+
reqs[objKey] = empty{}
697+
logger.V(consts.LogLevelDebug).Info("Enqueuing VaultStaticSecret instance",
698+
"objKey", objKey)
699+
logger.V(consts.LogLevelDebug).Info(
700+
"Sending GenericEvent to the SourceCh", "evt", evt)
701+
r.SourceCh <- evt
702+
}
703+
} else if err != nil {
704+
logger.V(consts.LogLevelWarning).Info(
705+
"Skipping, cacheKey error", "error", err)
706+
}
707+
}
708+
}
709+
632710
func newKVRequest(s secretsv1beta1.VaultStaticSecretSpec) (vault.ReadRequest, error) {
633711
var kvReq vault.ReadRequest
634712
switch s.Type {

docs/api/api-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,7 @@ sync the secret. This status is used during resource reconciliation.
10561056

10571057
_Appears in:_
10581058
- [VaultDynamicSecretStatus](#vaultdynamicsecretstatus)
1059+
- [VaultStaticSecretStatus](#vaultstaticsecretstatus)
10591060

10601061
| Field | Description | Default | Validation |
10611062
| --- | --- | --- | --- |

0 commit comments

Comments
 (0)