Skip to content

Commit 187eecc

Browse files
lmicciniclaude
andcommitted
Add secret rotation tracking with composite hash drift detection
Track which nodes in a nodeset have been deployed with the latest secret versions, enabling safe credential rotation by blocking deletion of old credentials until all nodes are updated. Design: - Compute a composite SHA-256 hash from all tracked secrets, sorted by name for determinism, using lib-common's secret.Hash() for content-based hashing - Store the deployed node list in a ConfigMap (<nodeset>-secret-tracking) to avoid bloating CR status for large nodesets. The ConfigMap contains {deployedSecretHash, deployedNodes[]} - Detect drift on every reconcile by fetching tracked secrets from the cluster and comparing the composite hash against DeployedSecretHash in status. On mismatch, set AllNodesUpdated=false and UpdatedNodes=0 immediately - Accumulate nodes across partial deployments using AnsibleLimit: when the deployment hash matches the stored hash, nodes are appended; when the hash differs (secrets changed), the node list resets - Skip stale deployments whose secret hashes no longer match the cluster state - Prune removed nodes by intersecting DeployedNodes with current Spec.Nodes SecretDeploymentStatus fields in NodeSet status: - AllNodesUpdated: all nodes deployed with current secrets (consumed by infra-operator's RabbitMQUser controller to block credential deletion) - TotalNodes / UpdatedNodes: node counts for observability - DeployedSecretHash: composite hash at last successful deployment - LastUpdateTime: timestamp of last status update API exports: - GetSecretTrackingConfigMapName(nodesetName) returns the deterministic ConfigMap name for a given nodeset, allowing consumers to reference it Watch configuration: - Secret watch uses ResourceVersionChangedPredicate to avoid reconciling on metadata-only updates - Field index on status.secretHashes.keys enables O(1) lookup of nodesets tracking a given secret, replacing O(secrets * nodesets) list scans Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 64e838e commit 187eecc

9 files changed

Lines changed: 1339 additions & 15 deletions

api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,33 @@ spec:
19871987
generation, then the controller has not processed the latest changes.
19881988
format: int64
19891989
type: integer
1990+
secretDeployment:
1991+
description: |-
1992+
SecretDeployment tracks secret deployment progress across nodeset nodes
1993+
Details are stored in a ConfigMap to avoid bloating the CR status
1994+
properties:
1995+
allNodesUpdated:
1996+
description: AllNodesUpdated indicates all nodes have been deployed
1997+
with current secret versions
1998+
type: boolean
1999+
deployedSecretHash:
2000+
description: |-
2001+
DeployedSecretHash is a composite hash of all tracked secrets at the time of
2002+
the last successful deployment. Drift is detected when this differs from the
2003+
current cluster secret state.
2004+
type: string
2005+
lastUpdateTime:
2006+
description: LastUpdateTime is when this status was last updated
2007+
format: date-time
2008+
type: string
2009+
totalNodes:
2010+
description: TotalNodes is the total number of nodes in the nodeset
2011+
type: integer
2012+
updatedNodes:
2013+
description: UpdatedNodes is count of nodes deployed with current
2014+
secret versions
2015+
type: integer
2016+
type: object
19902017
secretHashes:
19912018
additionalProperties:
19922019
type: string

api/dataplane/v1beta1/openstackdataplanenodeset_types.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,43 @@ type OpenStackDataPlaneNodeSetStatus struct {
163163

164164
//DeployedBmhHash - Hash of BMHs deployed
165165
DeployedBmhHash string `json:"deployedBmhHash,omitempty"`
166+
167+
// SecretDeployment tracks secret deployment progress across nodeset nodes
168+
// Details are stored in a ConfigMap to avoid bloating the CR status
169+
SecretDeployment *SecretDeploymentStatus `json:"secretDeployment,omitempty"`
170+
}
171+
172+
// GetSecretTrackingConfigMapName returns the name of the ConfigMap that stores
173+
// per-node secret deployment tracking data for a given nodeset.
174+
func GetSecretTrackingConfigMapName(nodesetName string) string {
175+
return fmt.Sprintf("%s-secret-tracking", nodesetName)
176+
}
177+
178+
// SecretDeploymentStatus tracks secret deployment progress across nodeset nodes.
179+
// Summary fields are stored here; the detailed per-node deployed list is in a
180+
// ConfigMap (<nodeset-name>-secret-tracking) to avoid bloating CR status for large nodesets.
181+
type SecretDeploymentStatus struct {
182+
// +optional
183+
// AllNodesUpdated indicates all nodes have been deployed with current secret versions
184+
AllNodesUpdated bool `json:"allNodesUpdated,omitempty"`
185+
186+
// +optional
187+
// TotalNodes is the total number of nodes in the nodeset
188+
TotalNodes int `json:"totalNodes,omitempty"`
189+
190+
// +optional
191+
// UpdatedNodes is count of nodes deployed with current secret versions
192+
UpdatedNodes int `json:"updatedNodes,omitempty"`
193+
194+
// +optional
195+
// DeployedSecretHash is a composite hash of all tracked secrets at the time of
196+
// the last successful deployment. Drift is detected when this differs from the
197+
// current cluster secret state.
198+
DeployedSecretHash string `json:"deployedSecretHash,omitempty"`
199+
200+
// +optional
201+
// LastUpdateTime is when this status was last updated
202+
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
166203
}
167204

168205
// +kubebuilder:object:root=true
@@ -183,6 +220,14 @@ func (instance OpenStackDataPlaneNodeSet) IsReady() bool {
183220
return instance.Status.Conditions.IsTrue(condition.ReadyCondition)
184221
}
185222

223+
// AreAllNodesUpdated returns true if all nodes have been deployed with current
224+
// secret versions. Returns false if tracking is not initialized or any nodes
225+
// have pending updates (fail-safe default).
226+
func (instance OpenStackDataPlaneNodeSet) AreAllNodesUpdated() bool {
227+
return instance.Status.SecretDeployment != nil &&
228+
instance.Status.SecretDeployment.AllNodesUpdated
229+
}
230+
186231
// InitConditions - Initializes Status Conditons
187232
func (instance *OpenStackDataPlaneNodeSet) InitConditions() {
188233
instance.Status.Conditions = condition.Conditions{}

api/dataplane/v1beta1/zz_generated.deepcopy.go

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

bindata/crds/crds.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21193,6 +21193,31 @@ spec:
2119321193
generation, then the controller has not processed the latest changes.
2119421194
format: int64
2119521195
type: integer
21196+
secretDeployment:
21197+
description: |-
21198+
SecretDeployment tracks secret deployment progress across nodeset nodes
21199+
Details are stored in a ConfigMap to avoid bloating the CR status
21200+
properties:
21201+
allNodesUpdated:
21202+
description: AllNodesUpdated indicates all nodes have current
21203+
versions of all tracked secrets
21204+
type: boolean
21205+
configMapName:
21206+
description: ConfigMapName references the ConfigMap containing
21207+
detailed per-secret tracking
21208+
type: string
21209+
lastUpdateTime:
21210+
description: LastUpdateTime is when this status was last updated
21211+
format: date-time
21212+
type: string
21213+
totalNodes:
21214+
description: TotalNodes is the total number of nodes in the nodeset
21215+
type: integer
21216+
updatedNodes:
21217+
description: UpdatedNodes is count of nodes with all current secret
21218+
versions
21219+
type: integer
21220+
type: object
2119621221
secretHashes:
2119721222
additionalProperties:
2119821223
type: string

config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,33 @@ spec:
19871987
generation, then the controller has not processed the latest changes.
19881988
format: int64
19891989
type: integer
1990+
secretDeployment:
1991+
description: |-
1992+
SecretDeployment tracks secret deployment progress across nodeset nodes
1993+
Details are stored in a ConfigMap to avoid bloating the CR status
1994+
properties:
1995+
allNodesUpdated:
1996+
description: AllNodesUpdated indicates all nodes have been deployed
1997+
with current secret versions
1998+
type: boolean
1999+
deployedSecretHash:
2000+
description: |-
2001+
DeployedSecretHash is a composite hash of all tracked secrets at the time of
2002+
the last successful deployment. Drift is detected when this differs from the
2003+
current cluster secret state.
2004+
type: string
2005+
lastUpdateTime:
2006+
description: LastUpdateTime is when this status was last updated
2007+
format: date-time
2008+
type: string
2009+
totalNodes:
2010+
description: TotalNodes is the total number of nodes in the nodeset
2011+
type: integer
2012+
updatedNodes:
2013+
description: UpdatedNodes is count of nodes deployed with current
2014+
secret versions
2015+
type: integer
2016+
type: object
19902017
secretHashes:
19912018
additionalProperties:
19922019
type: string

0 commit comments

Comments
 (0)