A Kubernetes operator that bridges Hardware Security Module (HSM) data storage with Kubernetes Secrets, providing true secret portability th
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rename, fix agent mirror

+275 -264
+2 -2
helm/hsm-secrets-operator/Chart.yaml
··· 2 2 name: hsm-secrets-operator 3 3 description: A Kubernetes operator that bridges Pico HSM binary data storage with Kubernetes Secrets 4 4 type: application 5 - version: 0.5.16 6 - appVersion: v0.5.16 5 + version: 0.5.17 6 + appVersion: v0.5.17 7 7 icon: https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/icon/color/kubernetes-icon-color.svg 8 8 home: https://github.com/evanjarrett/hsm-secrets-operator 9 9 sources:
+10 -2
internal/agent/deployment.go
··· 175 175 }, &deployment) 176 176 177 177 if err == nil { 178 - // Agent exists, but check if it's still targeting the right device/node 179 - needsUpdate := m.deploymentNeedsUpdateForDevice(&deployment, &aggregatedDevice) 178 + // Agent exists, but check if it needs updating (image version, device/node configuration) 179 + needsUpdate, err := m.agentNeedsUpdate(ctx, &deployment, hsmDevice) 180 + if err != nil { 181 + return fmt.Errorf("failed to check if agent deployment %s needs update: %w", agentName, err) 182 + } 183 + 184 + // Also check device-specific configuration 185 + if !needsUpdate { 186 + needsUpdate = m.deploymentNeedsUpdateForDevice(&deployment, &aggregatedDevice) 187 + } 180 188 181 189 if needsUpdate { 182 190 // Delete existing deployment to trigger recreation
+126
internal/controller/hsmmirror_controller.go
··· 1 + /* 2 + Copyright 2025. 3 + 4 + Licensed under the Apache License, Version 2.0 (the "License"); 5 + you may not use this file except in compliance with the License. 6 + You may obtain a copy of the License at 7 + 8 + http://www.apache.org/licenses/LICENSE-2.0 9 + 10 + Unless required by applicable law or agreed to in writing, software 11 + distributed under the License is distributed on an "AS IS" BASIS, 12 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 + See the License for the specific language governing permissions and 14 + limitations under the License. 15 + */ 16 + 17 + package controller 18 + 19 + import ( 20 + "context" 21 + "time" 22 + 23 + "k8s.io/apimachinery/pkg/runtime" 24 + ctrl "sigs.k8s.io/controller-runtime" 25 + "sigs.k8s.io/controller-runtime/pkg/client" 26 + "sigs.k8s.io/controller-runtime/pkg/log" 27 + 28 + hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1" 29 + "github.com/evanjarrett/hsm-secrets-operator/internal/agent" 30 + "github.com/evanjarrett/hsm-secrets-operator/internal/mirror" 31 + ) 32 + 33 + // HSMMirrorReconciler handles multi-device HSM mirroring and conflict resolution 34 + type HSMMirrorReconciler struct { 35 + client.Client 36 + Scheme *runtime.Scheme 37 + MirrorManager *mirror.MirrorManager 38 + 39 + // MirrorInterval controls how often to perform mirror checks (default: 30 seconds) 40 + MirrorInterval time.Duration 41 + } 42 + 43 + // NewHSMMirrorReconciler creates a new HSM mirror reconciler 44 + func NewHSMMirrorReconciler(k8sClient client.Client, scheme *runtime.Scheme, agentManager *agent.Manager, operatorNamespace string) *HSMMirrorReconciler { 45 + logger := ctrl.Log.WithName("hsm-mirror-controller") 46 + mirrorManager := mirror.NewMirrorManager(k8sClient, agentManager, logger, operatorNamespace) 47 + 48 + return &HSMMirrorReconciler{ 49 + Client: k8sClient, 50 + Scheme: scheme, 51 + MirrorManager: mirrorManager, 52 + MirrorInterval: 30 * time.Second, // Default mirror interval 53 + } 54 + } 55 + 56 + // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmsecrets,verbs=get;list;watch;update;patch 57 + // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmsecrets/status,verbs=get;update;patch 58 + // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmpools,verbs=get;list;watch 59 + // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmdevices,verbs=get;list;watch 60 + 61 + // Reconcile performs HSM device mirroring and conflict resolution 62 + func (r *HSMMirrorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 63 + logger := log.FromContext(ctx) 64 + 65 + // Fetch the HSMSecret instance 66 + var hsmSecret hsmv1alpha1.HSMSecret 67 + if err := r.Get(ctx, req.NamespacedName, &hsmSecret); err != nil { 68 + return ctrl.Result{}, client.IgnoreNotFound(err) 69 + } 70 + 71 + // Skip sync if AutoSync is disabled 72 + if !hsmSecret.Spec.AutoSync { 73 + logger.V(1).Info("AutoSync disabled, skipping sync", "secret", hsmSecret.Name) 74 + return ctrl.Result{}, nil 75 + } 76 + 77 + logger.Info("Starting multi-device HSM mirror", "secret", hsmSecret.Name) 78 + 79 + // Perform the mirror operation 80 + result, err := r.MirrorManager.MirrorSecret(ctx, &hsmSecret) 81 + if err != nil { 82 + logger.Error(err, "Failed to perform HSM mirror", "secret", hsmSecret.Name) 83 + 84 + // Update status with error 85 + hsmSecret.Status.SyncStatus = hsmv1alpha1.SyncStatusError 86 + hsmSecret.Status.LastError = err.Error() 87 + if updateErr := r.Status().Update(ctx, &hsmSecret); updateErr != nil { 88 + logger.Error(updateErr, "Failed to update HSMSecret status") 89 + } 90 + 91 + // Retry sooner on error 92 + return ctrl.Result{RequeueAfter: r.MirrorInterval / 2}, nil 93 + } 94 + 95 + // Update HSMSecret status with mirror results 96 + if err := r.MirrorManager.UpdateHSMSecretStatus(ctx, &hsmSecret, result); err != nil { 97 + logger.Error(err, "Failed to update HSMSecret status", "secret", hsmSecret.Name) 98 + return ctrl.Result{RequeueAfter: r.MirrorInterval / 2}, err 99 + } 100 + 101 + // Log mirror results 102 + logger.Info("Per-secret HSM mirror completed", 103 + "secret", hsmSecret.Name, 104 + "success", result.Success, 105 + "secretsProcessed", result.SecretsProcessed, 106 + "secretsUpdated", result.SecretsUpdated, 107 + "secretsCreated", result.SecretsCreated, 108 + "metadataRestored", result.MetadataRestored, 109 + "errors", len(result.Errors)) 110 + 111 + // Calculate next mirror interval based on HSMSecret spec 112 + mirrorInterval := r.MirrorInterval 113 + if hsmSecret.Spec.SyncInterval > 0 { 114 + mirrorInterval = time.Duration(hsmSecret.Spec.SyncInterval) * time.Second 115 + } 116 + 117 + return ctrl.Result{RequeueAfter: mirrorInterval}, nil 118 + } 119 + 120 + // SetupWithManager sets up the controller with the Manager. 121 + func (r *HSMMirrorReconciler) SetupWithManager(mgr ctrl.Manager) error { 122 + return ctrl.NewControllerManagedBy(mgr). 123 + For(&hsmv1alpha1.HSMSecret{}). 124 + Named("hsmmirror"). 125 + Complete(r) 126 + }
-126
internal/controller/hsmsync_controller.go
··· 1 - /* 2 - Copyright 2025. 3 - 4 - Licensed under the Apache License, Version 2.0 (the "License"); 5 - you may not use this file except in compliance with the License. 6 - You may obtain a copy of the License at 7 - 8 - http://www.apache.org/licenses/LICENSE-2.0 9 - 10 - Unless required by applicable law or agreed to in writing, software 11 - distributed under the License is distributed on an "AS IS" BASIS, 12 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 - See the License for the specific language governing permissions and 14 - limitations under the License. 15 - */ 16 - 17 - package controller 18 - 19 - import ( 20 - "context" 21 - "time" 22 - 23 - "k8s.io/apimachinery/pkg/runtime" 24 - ctrl "sigs.k8s.io/controller-runtime" 25 - "sigs.k8s.io/controller-runtime/pkg/client" 26 - "sigs.k8s.io/controller-runtime/pkg/log" 27 - 28 - hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1" 29 - "github.com/evanjarrett/hsm-secrets-operator/internal/agent" 30 - "github.com/evanjarrett/hsm-secrets-operator/internal/sync" 31 - ) 32 - 33 - // HSMSyncReconciler handles multi-device HSM synchronization and conflict resolution 34 - type HSMSyncReconciler struct { 35 - client.Client 36 - Scheme *runtime.Scheme 37 - SyncManager *sync.SyncManager 38 - 39 - // SyncInterval controls how often to perform sync checks (default: 30 seconds) 40 - SyncInterval time.Duration 41 - } 42 - 43 - // NewHSMSyncReconciler creates a new HSM sync reconciler 44 - func NewHSMSyncReconciler(k8sClient client.Client, scheme *runtime.Scheme, agentManager *agent.Manager) *HSMSyncReconciler { 45 - logger := ctrl.Log.WithName("hsm-sync-controller") 46 - syncManager := sync.NewSyncManager(k8sClient, agentManager, logger) 47 - 48 - return &HSMSyncReconciler{ 49 - Client: k8sClient, 50 - Scheme: scheme, 51 - SyncManager: syncManager, 52 - SyncInterval: 30 * time.Second, // Default sync interval 53 - } 54 - } 55 - 56 - // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmsecrets,verbs=get;list;watch;update;patch 57 - // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmsecrets/status,verbs=get;update;patch 58 - // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmpools,verbs=get;list;watch 59 - // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmdevices,verbs=get;list;watch 60 - 61 - // Reconcile performs HSM device synchronization and conflict resolution 62 - func (r *HSMSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 63 - logger := log.FromContext(ctx) 64 - 65 - // Fetch the HSMSecret instance 66 - var hsmSecret hsmv1alpha1.HSMSecret 67 - if err := r.Get(ctx, req.NamespacedName, &hsmSecret); err != nil { 68 - return ctrl.Result{}, client.IgnoreNotFound(err) 69 - } 70 - 71 - // Skip sync if AutoSync is disabled 72 - if !hsmSecret.Spec.AutoSync { 73 - logger.V(1).Info("AutoSync disabled, skipping sync", "secret", hsmSecret.Name) 74 - return ctrl.Result{}, nil 75 - } 76 - 77 - logger.Info("Starting multi-device HSM sync", "secret", hsmSecret.Name) 78 - 79 - // Perform the sync operation 80 - result, err := r.SyncManager.SyncSecret(ctx, &hsmSecret) 81 - if err != nil { 82 - logger.Error(err, "Failed to perform HSM sync", "secret", hsmSecret.Name) 83 - 84 - // Update status with error 85 - hsmSecret.Status.SyncStatus = hsmv1alpha1.SyncStatusError 86 - hsmSecret.Status.LastError = err.Error() 87 - if updateErr := r.Status().Update(ctx, &hsmSecret); updateErr != nil { 88 - logger.Error(updateErr, "Failed to update HSMSecret status") 89 - } 90 - 91 - // Retry sooner on error 92 - return ctrl.Result{RequeueAfter: r.SyncInterval / 2}, nil 93 - } 94 - 95 - // Update HSMSecret status with sync results 96 - if err := r.SyncManager.UpdateHSMSecretStatus(ctx, &hsmSecret, result); err != nil { 97 - logger.Error(err, "Failed to update HSMSecret status", "secret", hsmSecret.Name) 98 - return ctrl.Result{RequeueAfter: r.SyncInterval / 2}, err 99 - } 100 - 101 - // Log sync results 102 - logger.Info("Per-secret HSM sync completed", 103 - "secret", hsmSecret.Name, 104 - "success", result.Success, 105 - "secretsProcessed", result.SecretsProcessed, 106 - "secretsUpdated", result.SecretsUpdated, 107 - "secretsCreated", result.SecretsCreated, 108 - "metadataRestored", result.MetadataRestored, 109 - "errors", len(result.Errors)) 110 - 111 - // Calculate next sync interval based on HSMSecret spec 112 - syncInterval := r.SyncInterval 113 - if hsmSecret.Spec.SyncInterval > 0 { 114 - syncInterval = time.Duration(hsmSecret.Spec.SyncInterval) * time.Second 115 - } 116 - 117 - return ctrl.Result{RequeueAfter: syncInterval}, nil 118 - } 119 - 120 - // SetupWithManager sets up the controller with the Manager. 121 - func (r *HSMSyncReconciler) SetupWithManager(mgr ctrl.Manager) error { 122 - return ctrl.NewControllerManagedBy(mgr). 123 - For(&hsmv1alpha1.HSMSecret{}). 124 - Named("hsmsync"). 125 - Complete(r) 126 - }
+3 -3
internal/modes/manager/manager.go
··· 292 292 return err 293 293 } 294 294 295 - // Set up HSM sync controller for multi-device synchronization 296 - if err := controller.NewHSMSyncReconciler(mgr.GetClient(), mgr.GetScheme(), agentManager).SetupWithManager(mgr); err != nil { 297 - setupLog.Error(err, "unable to create controller", "controller", "HSMSync") 295 + // Set up HSM mirror controller for multi-device mirroring 296 + if err := controller.NewHSMMirrorReconciler(mgr.GetClient(), mgr.GetScheme(), agentManager, operatorNamespace).SetupWithManager(mgr); err != nil { 297 + setupLog.Error(err, "unable to create controller", "controller", "HSMMirror") 298 298 return err 299 299 } 300 300
+109 -106
internal/sync/manager.go internal/mirror/manager.go
··· 14 14 limitations under the License. 15 15 */ 16 16 17 - package sync 17 + package mirror 18 18 19 19 import ( 20 20 "context" ··· 31 31 "github.com/evanjarrett/hsm-secrets-operator/internal/hsm" 32 32 ) 33 33 34 - // AgentManagerInterface defines the interface for HSM agent management used by sync 34 + // AgentManagerInterface defines the interface for HSM agent management used by mirror 35 35 type AgentManagerInterface interface { 36 36 CreateSingleGRPCClient(ctx context.Context, deviceName, namespace string, logger logr.Logger) (hsm.Client, error) 37 37 } 38 38 39 - // SyncManager handles multi-device HSM synchronization and conflict resolution 40 - type SyncManager struct { 41 - client client.Client 42 - agentManager AgentManagerInterface 43 - logger logr.Logger 39 + // MirrorManager handles multi-device HSM mirroring and conflict resolution 40 + type MirrorManager struct { 41 + client client.Client 42 + agentManager AgentManagerInterface 43 + logger logr.Logger 44 + operatorNamespace string 44 45 } 45 46 46 - // NewSyncManager creates a new sync manager 47 - func NewSyncManager(k8sClient client.Client, agentManager AgentManagerInterface, logger logr.Logger) *SyncManager { 48 - return &SyncManager{ 49 - client: k8sClient, 50 - agentManager: agentManager, 51 - logger: logger.WithName("sync-manager"), 47 + // NewMirrorManager creates a new mirror manager 48 + func NewMirrorManager(k8sClient client.Client, agentManager AgentManagerInterface, logger logr.Logger, operatorNamespace string) *MirrorManager { 49 + return &MirrorManager{ 50 + client: k8sClient, 51 + agentManager: agentManager, 52 + logger: logger.WithName("mirror-manager"), 53 + operatorNamespace: operatorNamespace, 52 54 } 53 55 } 54 56 55 - // SyncResult represents the result of a per-secret synchronization operation 56 - type SyncResult struct { 57 + // MirrorResult represents the result of a per-secret mirroring operation 58 + type MirrorResult struct { 57 59 Success bool 58 60 SecretsProcessed int 59 61 SecretsUpdated int 60 62 SecretsCreated int 61 63 MetadataRestored int 62 - SecretResults map[string]SecretSyncResult 64 + SecretResults map[string]SecretMirrorResult 63 65 Errors []string 64 66 } 65 67 66 - // SecretSyncResult represents the result of syncing a specific secret 67 - type SecretSyncResult struct { 68 + // SecretMirrorResult represents the result of mirroring a specific secret 69 + type SecretMirrorResult struct { 68 70 SecretPath string 69 71 SourceDevice string 70 72 SourceVersion int64 71 73 TargetDevices []string 72 - SyncType SyncType 74 + MirrorType MirrorType 73 75 Success bool 74 76 Error error 75 77 } ··· 90 92 Error error 91 93 } 92 94 93 - // SecretSyncPlan represents the plan for syncing a specific secret 94 - type SecretSyncPlan struct { 95 + // SecretMirrorPlan represents the plan for mirroring a specific secret 96 + type SecretMirrorPlan struct { 95 97 SecretPath string 96 98 SourceDevice string 97 99 SourceVersion int64 98 100 TargetDevices []string 99 - SyncType SyncType 101 + MirrorType MirrorType 100 102 } 101 103 102 - // SyncType represents the type of sync operation needed 103 - type SyncType int 104 + // MirrorType represents the type of mirror operation needed 105 + type MirrorType int 104 106 105 107 const ( 106 - SyncTypeSkip SyncType = iota // Already in sync 107 - SyncTypeUpdate // Update existing secret 108 - SyncTypeCreate // Create missing secret 109 - SyncTypeRestoreMetadata // Add metadata to existing secret 108 + MirrorTypeSkip MirrorType = iota // Already in sync 109 + MirrorTypeUpdate // Update existing secret 110 + MirrorTypeCreate // Create missing secret 111 + MirrorTypeRestoreMetadata // Add metadata to existing secret 110 112 ) 111 113 112 - // SyncSecret performs per-secret synchronization across all HSM devices 113 - func (sm *SyncManager) SyncSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret) (*SyncResult, error) { 114 - logger := sm.logger.WithValues("secret", hsmSecret.Name, "namespace", hsmSecret.Namespace) 114 + // MirrorSecret performs per-secret mirroring across all HSM devices 115 + func (mm *MirrorManager) MirrorSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret) (*MirrorResult, error) { 116 + logger := mm.logger.WithValues("secret", hsmSecret.Name, "namespace", hsmSecret.Namespace) 115 117 secretPath := hsmSecret.Name 116 118 117 - // Get all available HSM devices from HSMPools 118 - devices, err := sm.getAvailableDevices(ctx, hsmSecret.Namespace) 119 + // Get all available HSM devices from HSMPools in the operator namespace 120 + devices, err := mm.getAvailableDevices(ctx, mm.operatorNamespace) 119 121 if err != nil { 120 122 return nil, fmt.Errorf("failed to get available devices: %w", err) 121 123 } 122 124 123 125 if len(devices) == 0 { 124 - return &SyncResult{ 126 + return &MirrorResult{ 125 127 Success: false, 126 - SecretResults: make(map[string]SecretSyncResult), 128 + SecretResults: make(map[string]SecretMirrorResult), 127 129 Errors: []string{"no HSM devices available"}, 128 130 }, fmt.Errorf("no HSM devices available") 129 131 } 130 132 131 - logger.Info("Starting per-secret sync", "devices", len(devices), "secretPath", secretPath) 133 + logger.Info("Starting per-secret mirror", "devices", len(devices), "secretPath", secretPath) 132 134 133 135 // Build inventory of all secrets across all devices 134 - inventory, err := sm.buildSecretInventory(ctx, []string{secretPath}, devices, hsmSecret.Namespace, logger) 136 + inventory, err := mm.buildSecretInventory(ctx, []string{secretPath}, devices, mm.operatorNamespace, logger) 135 137 if err != nil { 136 - return &SyncResult{ 138 + return &MirrorResult{ 137 139 Success: false, 138 - SecretResults: make(map[string]SecretSyncResult), 140 + SecretResults: make(map[string]SecretMirrorResult), 139 141 Errors: []string{fmt.Sprintf("failed to build secret inventory: %v", err)}, 140 142 }, fmt.Errorf("failed to build secret inventory: %w", err) 141 143 } 142 144 143 - // Create sync plan for the single secret 144 - syncPlans := sm.createSyncPlans(inventory, logger) 145 + // Create mirror plan for the single secret 146 + mirrorPlans := mm.createMirrorPlans(inventory, logger) 145 147 146 - // Execute sync operations 147 - result := sm.executeSyncPlans(ctx, syncPlans, hsmSecret.Namespace, logger) 148 + // Execute mirror operations 149 + result := mm.executeMirrorPlans(ctx, mirrorPlans, mm.operatorNamespace, logger) 148 150 149 151 logger.Info("Per-secret sync completed", 150 152 "secretsProcessed", result.SecretsProcessed, ··· 157 159 } 158 160 159 161 // buildSecretInventory builds a comprehensive inventory of secrets across all devices 160 - func (sm *SyncManager) buildSecretInventory(ctx context.Context, secretPaths []string, devices []string, namespace string, logger logr.Logger) (map[string]*SecretInventory, error) { 162 + func (mm *MirrorManager) buildSecretInventory(ctx context.Context, secretPaths []string, devices []string, operatorNamespace string, logger logr.Logger) (map[string]*SecretInventory, error) { 161 163 inventory := make(map[string]*SecretInventory) 162 164 163 165 // Initialize inventory entries for requested secrets ··· 172 174 for _, deviceName := range devices { 173 175 logger.Info("Checking device for secrets", "device", deviceName, "secretCount", len(secretPaths)) 174 176 175 - // Create gRPC client for this device 176 - grpcClient, err := sm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 177 + // Create gRPC client for this device (agents are in operator namespace) 178 + grpcClient, err := mm.agentManager.CreateSingleGRPCClient(ctx, deviceName, operatorNamespace, logger) 177 179 if err != nil { 178 180 logger.Error(err, "Failed to create gRPC client", "device", deviceName) 179 181 // Mark all secrets as having an error on this device ··· 232 234 } else { 233 235 // Secret exists, calculate checksum 234 236 state.Present = true 235 - state.Checksum = sm.calculateChecksum(data) 237 + state.Checksum = mm.calculateChecksum(data) 236 238 logger.V(1).Info("Secret found on device", "device", deviceName, "secret", secretPath, "checksum", state.Checksum[:8]) 237 239 238 240 // Try to read metadata ··· 264 266 return inventory, nil 265 267 } 266 268 267 - // createSyncPlans analyzes secret inventory and creates sync plans for each secret 268 - func (sm *SyncManager) createSyncPlans(inventory map[string]*SecretInventory, logger logr.Logger) []*SecretSyncPlan { 269 - var plans []*SecretSyncPlan 269 + // createMirrorPlans analyzes secret inventory and creates sync plans for each secret 270 + func (mm *MirrorManager) createMirrorPlans(inventory map[string]*SecretInventory, logger logr.Logger) []*SecretMirrorPlan { 271 + var plans []*SecretMirrorPlan 270 272 271 273 for secretPath, secretInventory := range inventory { 272 - plan := sm.createSyncPlanForSecret(secretPath, secretInventory, logger) 274 + plan := mm.createMirrorPlanForSecret(secretPath, secretInventory, logger) 273 275 if plan != nil { 274 276 plans = append(plans, plan) 275 277 } ··· 278 280 return plans 279 281 } 280 282 281 - // createSyncPlanForSecret creates a sync plan for a specific secret across all devices 282 - func (sm *SyncManager) createSyncPlanForSecret(secretPath string, inventory *SecretInventory, logger logr.Logger) *SecretSyncPlan { 283 + // createMirrorPlanForSecret creates a sync plan for a specific secret across all devices 284 + func (mm *MirrorManager) createMirrorPlanForSecret(secretPath string, inventory *SecretInventory, logger logr.Logger) *SecretMirrorPlan { 283 285 // Find the authoritative source device (highest version, most recent timestamp) 284 286 var sourceDevice string 285 287 var sourceVersion int64 = -1 ··· 345 347 346 348 if allInSync { 347 349 logger.V(1).Info("Secret already in sync across all devices", "secret", secretPath) 348 - return &SecretSyncPlan{ 350 + return &SecretMirrorPlan{ 349 351 SecretPath: secretPath, 350 352 SourceDevice: sourceDevice, 351 353 SourceVersion: sourceVersion, 352 354 TargetDevices: []string{}, // No targets needed 353 - SyncType: SyncTypeSkip, 355 + MirrorType: MirrorTypeSkip, 354 356 } 355 357 } 356 358 } 357 359 358 360 // Determine target devices that need updates 359 361 var targetDevices []string 360 - var syncType SyncType 362 + var syncType MirrorType 361 363 362 364 // Add devices that need the secret created 363 365 targetDevices = append(targetDevices, devicesNeedingSecret...) 364 366 if len(devicesNeedingSecret) > 0 { 365 - syncType = SyncTypeCreate 367 + syncType = MirrorTypeCreate 366 368 } 367 369 368 370 // Add devices that need metadata restoration 369 371 targetDevices = append(targetDevices, devicesNeedingMetadata...) 370 - if len(devicesNeedingMetadata) > 0 && syncType != SyncTypeCreate { 371 - syncType = SyncTypeRestoreMetadata 372 + if len(devicesNeedingMetadata) > 0 && syncType != MirrorTypeCreate { 373 + syncType = MirrorTypeRestoreMetadata 372 374 } 373 375 374 376 // Add devices that have outdated versions ··· 377 379 if state.Version < sourceVersion { 378 380 // This device has an older version 379 381 targetDevices = append(targetDevices, deviceName) 380 - if syncType != SyncTypeCreate && syncType != SyncTypeRestoreMetadata { 381 - syncType = SyncTypeUpdate 382 + if syncType != MirrorTypeCreate && syncType != MirrorTypeRestoreMetadata { 383 + syncType = MirrorTypeUpdate 382 384 } 383 385 } 384 386 } ··· 392 394 393 395 if len(targetDevices) == 0 { 394 396 // No targets needed 395 - return &SecretSyncPlan{ 397 + return &SecretMirrorPlan{ 396 398 SecretPath: secretPath, 397 399 SourceDevice: sourceDevice, 398 400 SourceVersion: sourceVersion, 399 401 TargetDevices: []string{}, 400 - SyncType: SyncTypeSkip, 402 + MirrorType: MirrorTypeSkip, 401 403 } 402 404 } 403 405 ··· 405 407 "sourceDevice", sourceDevice, "sourceVersion", sourceVersion, 406 408 "targetDevices", len(targetDevices), "syncType", syncType) 407 409 408 - return &SecretSyncPlan{ 410 + return &SecretMirrorPlan{ 409 411 SecretPath: secretPath, 410 412 SourceDevice: sourceDevice, 411 413 SourceVersion: sourceVersion, 412 414 TargetDevices: targetDevices, 413 - SyncType: syncType, 415 + MirrorType: syncType, 414 416 } 415 417 } 416 418 ··· 442 444 return result 443 445 } 444 446 445 - // executeSyncPlans executes sync operations for all planned secret synchronizations 446 - func (sm *SyncManager) executeSyncPlans(ctx context.Context, plans []*SecretSyncPlan, namespace string, logger logr.Logger) *SyncResult { 447 - result := &SyncResult{ 447 + // executeMirrorPlans executes sync operations for all planned secret synchronizations 448 + func (mm *MirrorManager) executeMirrorPlans(ctx context.Context, plans []*SecretMirrorPlan, operatorNamespace string, logger logr.Logger) *MirrorResult { 449 + result := &MirrorResult{ 448 450 Success: true, 449 451 SecretsProcessed: len(plans), 450 452 SecretsUpdated: 0, 451 453 SecretsCreated: 0, 452 454 MetadataRestored: 0, 453 - SecretResults: make(map[string]SecretSyncResult), 455 + SecretResults: make(map[string]SecretMirrorResult), 454 456 Errors: []string{}, 455 457 } 456 458 457 459 for _, plan := range plans { 458 - secretResult := sm.executeSyncPlan(ctx, plan, namespace, logger) 460 + secretResult := mm.executeMirrorPlan(ctx, plan, operatorNamespace, logger) 459 461 result.SecretResults[plan.SecretPath] = secretResult 460 462 461 463 if secretResult.Success { 462 - switch secretResult.SyncType { 463 - case SyncTypeCreate: 464 + switch secretResult.MirrorType { 465 + case MirrorTypeCreate: 464 466 result.SecretsCreated++ 465 - case SyncTypeUpdate: 467 + case MirrorTypeUpdate: 466 468 result.SecretsUpdated++ 467 - case SyncTypeRestoreMetadata: 469 + case MirrorTypeRestoreMetadata: 468 470 result.MetadataRestored++ 469 471 } 470 472 } else { ··· 478 480 return result 479 481 } 480 482 481 - // executeSyncPlan executes a single secret sync plan 482 - func (sm *SyncManager) executeSyncPlan(ctx context.Context, plan *SecretSyncPlan, namespace string, logger logr.Logger) SecretSyncResult { 483 - result := SecretSyncResult{ 483 + // executeMirrorPlan executes a single secret sync plan 484 + func (mm *MirrorManager) executeMirrorPlan(ctx context.Context, plan *SecretMirrorPlan, operatorNamespace string, logger logr.Logger) SecretMirrorResult { 485 + result := SecretMirrorResult{ 484 486 SecretPath: plan.SecretPath, 485 487 SourceDevice: plan.SourceDevice, 486 488 SourceVersion: plan.SourceVersion, 487 489 TargetDevices: plan.TargetDevices, 488 - SyncType: plan.SyncType, 490 + MirrorType: plan.MirrorType, 489 491 Success: false, 490 492 Error: nil, 491 493 } 492 494 493 495 // Skip if no sync needed 494 - if plan.SyncType == SyncTypeSkip { 496 + if plan.MirrorType == MirrorTypeSkip { 495 497 result.Success = true 496 498 logger.V(1).Info("Skipping sync - already in sync", "secret", plan.SecretPath) 497 499 return result 498 500 } 499 501 500 502 // Get source data and metadata 501 - sourceData, sourceMetadata, err := sm.readSecretWithMetadata(ctx, plan.SourceDevice, plan.SecretPath, namespace, logger) 503 + sourceData, sourceMetadata, err := mm.readSecretWithMetadata(ctx, plan.SourceDevice, plan.SecretPath, operatorNamespace, logger) 502 504 if err != nil { 503 505 result.Error = fmt.Errorf("failed to read source secret: %w", err) 504 506 logger.Error(err, "Failed to read source secret", "device", plan.SourceDevice, "secret", plan.SecretPath) ··· 525 527 } 526 528 527 529 // Handle metadata restoration on source device if needed 528 - if plan.SyncType == SyncTypeRestoreMetadata { 530 + if plan.MirrorType == MirrorTypeRestoreMetadata { 529 531 if sourceMetadata == nil || sourceMetadata.Labels == nil || sourceMetadata.Labels["sync.version"] == "" { 530 532 logger.Info("Restoring metadata on source device", "device", plan.SourceDevice, "secret", plan.SecretPath) 531 - if err := sm.writeSecretWithMetadata(ctx, plan.SourceDevice, plan.SecretPath, sourceData, syncMetadata, namespace, logger); err != nil { 533 + if err := mm.writeSecretWithMetadata(ctx, plan.SourceDevice, plan.SecretPath, sourceData, syncMetadata, operatorNamespace, logger); err != nil { 532 534 result.Error = fmt.Errorf("failed to restore metadata on source: %w", err) 533 535 return result 534 536 } ··· 541 543 logger.Info("Syncing secret to target device", "secret", plan.SecretPath, "source", plan.SourceDevice, "target", targetDevice, "version", newVersion) 542 544 543 545 var syncErr error 544 - switch plan.SyncType { 545 - case SyncTypeCreate, SyncTypeUpdate: 546 - syncErr = sm.writeSecretWithMetadata(ctx, targetDevice, plan.SecretPath, sourceData, syncMetadata, namespace, logger) 547 - case SyncTypeRestoreMetadata: 546 + switch plan.MirrorType { 547 + case MirrorTypeCreate, MirrorTypeUpdate: 548 + syncErr = mm.writeSecretWithMetadata(ctx, targetDevice, plan.SecretPath, sourceData, syncMetadata, operatorNamespace, logger) 549 + case MirrorTypeRestoreMetadata: 548 550 // For metadata restoration, we just update the metadata without changing the data 549 - syncErr = sm.writeMetadataOnly(ctx, targetDevice, plan.SecretPath, syncMetadata, namespace, logger) 551 + syncErr = mm.writeMetadataOnly(ctx, targetDevice, plan.SecretPath, syncMetadata, operatorNamespace, logger) 550 552 } 551 553 552 554 if syncErr != nil { ··· 565 567 566 568 if result.Success { 567 569 logger.Info("Sync plan executed successfully", "secret", plan.SecretPath, 568 - "syncType", plan.SyncType, "targetCount", successfulTargets) 570 + "syncType", plan.MirrorType, "targetCount", successfulTargets) 569 571 } 570 572 571 573 return result 572 574 } 573 575 574 576 // readSecretWithMetadata reads both secret data and metadata from a device 575 - func (sm *SyncManager) readSecretWithMetadata(ctx context.Context, deviceName, secretPath, namespace string, logger logr.Logger) (hsm.SecretData, *hsm.SecretMetadata, error) { 576 - grpcClient, err := sm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 577 + func (mm *MirrorManager) readSecretWithMetadata(ctx context.Context, deviceName, secretPath, namespace string, logger logr.Logger) (hsm.SecretData, *hsm.SecretMetadata, error) { 578 + grpcClient, err := mm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 577 579 if err != nil { 578 580 return nil, nil, fmt.Errorf("failed to create gRPC client: %w", err) 579 581 } ··· 604 606 } 605 607 606 608 // writeSecretWithMetadata writes both secret data and metadata to a device 607 - func (sm *SyncManager) writeSecretWithMetadata(ctx context.Context, deviceName, secretPath string, data hsm.SecretData, metadata *hsm.SecretMetadata, namespace string, logger logr.Logger) error { 608 - grpcClient, err := sm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 609 + func (mm *MirrorManager) writeSecretWithMetadata(ctx context.Context, deviceName, secretPath string, data hsm.SecretData, metadata *hsm.SecretMetadata, namespace string, logger logr.Logger) error { 610 + grpcClient, err := mm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 609 611 if err != nil { 610 612 return fmt.Errorf("failed to create gRPC client: %w", err) 611 613 } ··· 628 630 } 629 631 630 632 // writeMetadataOnly updates only the metadata for an existing secret 631 - func (sm *SyncManager) writeMetadataOnly(ctx context.Context, deviceName, secretPath string, metadata *hsm.SecretMetadata, namespace string, logger logr.Logger) error { 632 - grpcClient, err := sm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 633 + func (mm *MirrorManager) writeMetadataOnly(ctx context.Context, deviceName, secretPath string, metadata *hsm.SecretMetadata, namespace string, logger logr.Logger) error { 634 + grpcClient, err := mm.agentManager.CreateSingleGRPCClient(ctx, deviceName, namespace, logger) 633 635 if err != nil { 634 636 return fmt.Errorf("failed to create gRPC client: %w", err) 635 637 } ··· 657 659 return nil 658 660 } 659 661 660 - // getAvailableDevices gets list of available HSM devices from HSMPools 661 - func (sm *SyncManager) getAvailableDevices(ctx context.Context, namespace string) ([]string, error) { 662 + // getAvailableDevices gets list of available HSM devices from HSMPools in the operator namespace 663 + func (mm *MirrorManager) getAvailableDevices(ctx context.Context, operatorNamespace string) ([]string, error) { 662 664 var hsmPoolList hsmv1alpha1.HSMPoolList 663 - if err := sm.client.List(ctx, &hsmPoolList, client.InNamespace(namespace)); err != nil { 664 - return nil, fmt.Errorf("failed to list HSM pools: %w", err) 665 + // HSMPools are always in the operator namespace (where controller-manager runs) 666 + if err := mm.client.List(ctx, &hsmPoolList, client.InNamespace(operatorNamespace)); err != nil { 667 + return nil, fmt.Errorf("failed to list HSM pools in operator namespace %s: %w", operatorNamespace, err) 665 668 } 666 669 667 670 deviceNames := make(map[string]bool) ··· 683 686 return devices, nil 684 687 } 685 688 686 - // UpdateHSMSecretStatus updates the HSMSecret status with sync results 687 - func (sm *SyncManager) UpdateHSMSecretStatus(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, result *SyncResult) error { 689 + // UpdateHSMSecretStatus updates the HSMSecret status with mirror results 690 + func (mm *MirrorManager) UpdateHSMSecretStatus(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, result *MirrorResult) error { 688 691 now := metav1.NewTime(time.Now()) 689 692 690 693 // Update overall status ··· 712 715 // Calculate checksum from the source device if sync was successful 713 716 if secretResult.Success { 714 717 // Read the current data to calculate checksum 715 - if devices, err := sm.getAvailableDevices(ctx, hsmSecret.Namespace); err == nil && len(devices) > 0 { 716 - if data, _, err := sm.readSecretWithMetadata(ctx, devices[0], hsmSecret.Name, hsmSecret.Namespace, sm.logger); err == nil { 717 - hsmSecret.Status.SecretChecksum = sm.calculateChecksum(data) 718 + if devices, err := mm.getAvailableDevices(ctx, mm.operatorNamespace); err == nil && len(devices) > 0 { 719 + if data, _, err := mm.readSecretWithMetadata(ctx, devices[0], hsmSecret.Name, mm.operatorNamespace, mm.logger); err == nil { 720 + hsmSecret.Status.SecretChecksum = mm.calculateChecksum(data) 718 721 } 719 722 } 720 723 } ··· 726 729 } 727 730 728 731 // Update device-specific sync status based on available devices 729 - devices, err := sm.getAvailableDevices(ctx, hsmSecret.Namespace) 732 + devices, err := mm.getAvailableDevices(ctx, mm.operatorNamespace) 730 733 if err == nil { 731 734 hsmSecret.Status.DeviceSyncStatus = make([]hsmv1alpha1.HSMDeviceSync, 0, len(devices)) 732 735 ··· 778 781 } 779 782 } 780 783 781 - return sm.client.Status().Update(ctx, hsmSecret) 784 + return mm.client.Status().Update(ctx, hsmSecret) 782 785 } 783 786 784 787 // calculateChecksum calculates SHA256 checksum of secret data 785 - func (sm *SyncManager) calculateChecksum(data hsm.SecretData) string { 788 + func (mm *MirrorManager) calculateChecksum(data hsm.SecretData) string { 786 789 if data == nil { 787 790 return "" 788 791 }
+25 -25
internal/sync/manager_test.go internal/mirror/manager_test.go
··· 14 14 limitations under the License. 15 15 */ 16 16 17 - package sync 17 + package mirror 18 18 19 19 import ( 20 20 "context" ··· 37 37 return hsm.NewMockClient(), nil 38 38 } 39 39 40 - func TestNewSyncManager(t *testing.T) { 40 + func TestNewMirrorManager(t *testing.T) { 41 41 scheme := runtime.NewScheme() 42 42 _ = hsmv1alpha1.AddToScheme(scheme) 43 43 44 44 client := fake.NewClientBuilder().WithScheme(scheme).Build() 45 45 mockAgentManager := &MockAgentManager{} 46 46 47 - syncManager := NewSyncManager(client, mockAgentManager, logr.Discard()) 47 + mirrorManager := NewMirrorManager(client, mockAgentManager, logr.Discard(), "test-namespace") 48 48 49 - assert.NotNil(t, syncManager) 50 - assert.NotNil(t, syncManager.client) 51 - assert.NotNil(t, syncManager.agentManager) 52 - assert.NotNil(t, syncManager.logger) 49 + assert.NotNil(t, mirrorManager) 50 + assert.NotNil(t, mirrorManager.client) 51 + assert.NotNil(t, mirrorManager.agentManager) 52 + assert.NotNil(t, mirrorManager.logger) 53 53 } 54 54 55 - func TestSyncResult_Structure(t *testing.T) { 56 - // Test the new SyncResult structure 57 - result := &SyncResult{ 55 + func TestMirrorResult_Structure(t *testing.T) { 56 + // Test the new MirrorResult structure 57 + result := &MirrorResult{ 58 58 Success: true, 59 59 SecretsProcessed: 3, 60 60 SecretsUpdated: 1, 61 61 SecretsCreated: 1, 62 62 MetadataRestored: 1, 63 - SecretResults: map[string]SecretSyncResult{ 63 + SecretResults: map[string]SecretMirrorResult{ 64 64 "secret1": { 65 65 SecretPath: "secret1", 66 66 SourceDevice: "device1", 67 67 SourceVersion: 123, 68 68 TargetDevices: []string{"device2"}, 69 - SyncType: SyncTypeUpdate, 69 + MirrorType: MirrorTypeUpdate, 70 70 Success: true, 71 71 Error: nil, 72 72 }, ··· 75 75 SourceDevice: "device2", 76 76 SourceVersion: 456, 77 77 TargetDevices: []string{"device1"}, 78 - SyncType: SyncTypeCreate, 78 + MirrorType: MirrorTypeCreate, 79 79 Success: true, 80 80 Error: nil, 81 81 }, ··· 96 96 assert.Equal(t, "secret1", secret1Result.SecretPath) 97 97 assert.Equal(t, "device1", secret1Result.SourceDevice) 98 98 assert.Equal(t, int64(123), secret1Result.SourceVersion) 99 - assert.Equal(t, SyncTypeUpdate, secret1Result.SyncType) 99 + assert.Equal(t, MirrorTypeUpdate, secret1Result.MirrorType) 100 100 assert.True(t, secret1Result.Success) 101 101 } 102 102 103 - func TestSyncTypes(t *testing.T) { 104 - // Test that SyncType constants are correctly defined 105 - assert.Equal(t, SyncType(0), SyncTypeSkip) 106 - assert.Equal(t, SyncType(1), SyncTypeUpdate) 107 - assert.Equal(t, SyncType(2), SyncTypeCreate) 108 - assert.Equal(t, SyncType(3), SyncTypeRestoreMetadata) 103 + func TestMirrorTypes(t *testing.T) { 104 + // Test that MirrorType constants are correctly defined 105 + assert.Equal(t, MirrorType(0), MirrorTypeSkip) 106 + assert.Equal(t, MirrorType(1), MirrorTypeUpdate) 107 + assert.Equal(t, MirrorType(2), MirrorTypeCreate) 108 + assert.Equal(t, MirrorType(3), MirrorTypeRestoreMetadata) 109 109 } 110 110 111 - func TestSecretSyncResult_Structure(t *testing.T) { 112 - // Test that SecretSyncResult has the expected fields 113 - result := SecretSyncResult{ 111 + func TestSecretMirrorResult_Structure(t *testing.T) { 112 + // Test that SecretMirrorResult has the expected fields 113 + result := SecretMirrorResult{ 114 114 SecretPath: "test-secret", 115 115 SourceDevice: "device1", 116 116 SourceVersion: 123, 117 117 TargetDevices: []string{"device2", "device3"}, 118 - SyncType: SyncTypeCreate, 118 + MirrorType: MirrorTypeCreate, 119 119 Success: true, 120 120 Error: nil, 121 121 } ··· 124 124 assert.Equal(t, "device1", result.SourceDevice) 125 125 assert.Equal(t, int64(123), result.SourceVersion) 126 126 assert.Equal(t, []string{"device2", "device3"}, result.TargetDevices) 127 - assert.Equal(t, SyncTypeCreate, result.SyncType) 127 + assert.Equal(t, MirrorTypeCreate, result.MirrorType) 128 128 assert.True(t, result.Success) 129 129 assert.Nil(t, result.Error) 130 130 }