diff --git a/api/v1beta1/vaultstaticsecret_types.go b/api/v1beta1/vaultstaticsecret_types.go index 86c8ac19..217ec8be 100644 --- a/api/v1beta1/vaultstaticsecret_types.go +++ b/api/v1beta1/vaultstaticsecret_types.go @@ -75,6 +75,9 @@ type VaultStaticSecretStatus struct { // The SecretMac is also used to detect drift in the Destination Secret's Data. // If drift is detected the data will be synced to the Destination. SecretMAC string `json:"secretMAC,omitempty"` + // VaultClientMeta contains the status of the Vault client and is used during + // resource reconciliation. + VaultClientMeta VaultClientMeta `json:"vaultClientMeta,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 0a90ae4a..8927df43 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1528,6 +1528,7 @@ func (in *VaultStaticSecretSpec) DeepCopy() *VaultStaticSecretSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultStaticSecretStatus) DeepCopyInto(out *VaultStaticSecretStatus) { *out = *in + out.VaultClientMeta = in.VaultClientMeta } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultStaticSecretStatus. diff --git a/chart/crds/secrets.hashicorp.com_vaultstaticsecrets.yaml b/chart/crds/secrets.hashicorp.com_vaultstaticsecrets.yaml index d6c20758..730666e6 100644 --- a/chart/crds/secrets.hashicorp.com_vaultstaticsecrets.yaml +++ b/chart/crds/secrets.hashicorp.com_vaultstaticsecrets.yaml @@ -304,6 +304,21 @@ spec: The SecretMac is also used to detect drift in the Destination Secret's Data. If drift is detected the data will be synced to the Destination. type: string + vaultClientMeta: + description: |- + VaultClientMeta contains the status of the Vault client and is used during + resource reconciliation. + properties: + cacheKey: + description: CacheKey is the unique key used to identify the client + cache. + type: string + id: + description: |- + ID is the Vault ID of the authenticated client. The ID should never contain + any sensitive information. + type: string + type: object required: - lastGeneration type: object diff --git a/config/crd/bases/secrets.hashicorp.com_vaultstaticsecrets.yaml b/config/crd/bases/secrets.hashicorp.com_vaultstaticsecrets.yaml index d6c20758..730666e6 100644 --- a/config/crd/bases/secrets.hashicorp.com_vaultstaticsecrets.yaml +++ b/config/crd/bases/secrets.hashicorp.com_vaultstaticsecrets.yaml @@ -304,6 +304,21 @@ spec: The SecretMac is also used to detect drift in the Destination Secret's Data. If drift is detected the data will be synced to the Destination. type: string + vaultClientMeta: + description: |- + VaultClientMeta contains the status of the Vault client and is used during + resource reconciliation. + properties: + cacheKey: + description: CacheKey is the unique key used to identify the client + cache. + type: string + id: + description: |- + ID is the Vault ID of the authenticated client. The ID should never contain + any sensitive information. + type: string + type: object required: - lastGeneration type: object diff --git a/controllers/vaultstaticsecret_controller.go b/controllers/vaultstaticsecret_controller.go index ea1097d3..f1f1c986 100644 --- a/controllers/vaultstaticsecret_controller.go +++ b/controllers/vaultstaticsecret_controller.go @@ -96,6 +96,48 @@ func (r *VaultStaticSecretReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{RequeueAfter: computeHorizonWithJitter(requeueDurationOnError)}, nil } + destExists, _ := helpers.CheckSecretExists(ctx, r.Client, o) + if !o.Spec.Destination.Create && !destExists { + logger.Info("Destination secret does not exist, either create it or "+ + "set .spec.destination.create=true", "destination", o.Spec.Destination) + return ctrl.Result{RequeueAfter: requeueDurationOnError}, nil + } + + // we can ignore the error here, since it was handled above in the Get() call. + clientCacheKey, _ := c.GetCacheKey() + lastClientCacheKey := o.Status.VaultClientMeta.CacheKey + lastClientID := o.Status.VaultClientMeta.ID + + // update the VaultClientMeta in the resource's status. + o.Status.VaultClientMeta.CacheKey = clientCacheKey.String() + o.Status.VaultClientMeta.ID = c.ID() + + var syncReason string + switch { + // indicates that the resource has not been synced yet. + case o.Status.LastGeneration == 0: + syncReason = consts.ReasonInitialSync + // indicates that the resource has been updated since the last sync. + case o.GetGeneration() != o.Status.LastGeneration: + syncReason = consts.ReasonResourceUpdated + // indicates that the destination secret does not exist and the resource is configured to create it. + case o.Spec.Destination.Create && !destExists: + syncReason = consts.ReasonInexistentDestination + // indicates that the cache key has changed since the last sync. This can happen + // when the VaultAuth or VaultConnection objects are updated since the last sync. + case lastClientCacheKey != "" && lastClientCacheKey != o.Status.VaultClientMeta.CacheKey: + syncReason = consts.ReasonVaultClientConfigChanged + // indicates that the Vault client ID has changed since the last sync. This can + // happen when the client has re-authenticated to Vault since the last sync. + case lastClientID != "" && lastClientID != o.Status.VaultClientMeta.ID: + syncReason = consts.ReasonVaultTokenRotated + // indicates that the secret has been changed in Vault since the last sync. + case o.GetGeneration() > 0: + syncReason = consts.ReasonSecretRotated + default: + syncReason = consts.ReasonSecretSynced + } + var requeueAfter time.Duration if o.Spec.RefreshAfter != "" { d, err := parseDurationString(o.Spec.RefreshAfter, ".spec.refreshAfter", 0) @@ -183,14 +225,12 @@ func (r *VaultStaticSecretReconciler) Reconcile(ctx context.Context, req ctrl.Re "Failed to update k8s secret: %s", err) return ctrl.Result{RequeueAfter: computeHorizonWithJitter(requeueDurationOnError)}, nil } - reason := consts.ReasonSecretSynced if doRolloutRestart { - reason = consts.ReasonSecretRotated // rollout-restart errors are not retryable // all error reporting is handled by helpers.HandleRolloutRestarts _ = helpers.HandleRolloutRestarts(ctx, r.Client, o, r.Recorder) } - r.Recorder.Event(o, corev1.EventTypeNormal, reason, "Secret synced") + r.Recorder.Event(o, corev1.EventTypeNormal, syncReason, "Secret synced") } else { logger.V(consts.LogLevelDebug).Info("Secret sync not required") } @@ -585,8 +625,6 @@ func (r *VaultStaticSecretReconciler) vaultClientCallback(ctx context.Context, c reqs[objKey] = empty{} logger.V(consts.LogLevelDebug).Info("Enqueuing VaultStaticSecret instance", "objKey", objKey) - // TODO(tvoran): not sure we need a sync registry for VSS? - // r.SyncRegistry.Add(objKey) logger.V(consts.LogLevelDebug).Info( "Sending GenericEvent to the SourceCh", "evt", evt) r.SourceCh <- evt diff --git a/docs/api/api-reference.md b/docs/api/api-reference.md index 402bee2d..5c4ca97c 100644 --- a/docs/api/api-reference.md +++ b/docs/api/api-reference.md @@ -821,6 +821,7 @@ sync the secret. This status is used during resource reconciliation. _Appears in:_ - [VaultDynamicSecretStatus](#vaultdynamicsecretstatus) +- [VaultStaticSecretStatus](#vaultstaticsecretstatus) | Field | Description | Default | Validation | | --- | --- | --- | --- |