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.

clean up hsmsecret_controller

+130 -411
+32 -2
internal/agent/deployment.go
··· 919 919 920 920 // Ensure starts and ends with alphanumeric 921 921 sanitized = strings.TrimFunc(sanitized, func(r rune) bool { 922 - return !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')) 922 + return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') 923 923 }) 924 924 925 925 // Kubernetes label values have a 63 character limit ··· 927 927 sanitized = sanitized[:63] 928 928 // Re-trim end if we cut off at a non-alphanumeric 929 929 sanitized = strings.TrimFunc(sanitized, func(r rune) bool { 930 - return !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')) 930 + return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') 931 931 }) 932 932 } 933 933 ··· 975 975 } 976 976 977 977 return grpcClient, nil 978 + } 979 + 980 + // GetAvailableDevices finds all devices with ready HSMPools and active agents 981 + func (m *Manager) GetAvailableDevices(ctx context.Context, namespace string) ([]string, error) { 982 + // List all HSMPools to find all with active agents 983 + var hsmPoolList hsmv1alpha1.HSMPoolList 984 + if err := m.List(ctx, &hsmPoolList, client.InNamespace(namespace)); err != nil { 985 + return nil, fmt.Errorf("failed to list HSM pools: %w", err) 986 + } 987 + 988 + var availableDevices []string 989 + // Check all pools that have active agents 990 + for _, pool := range hsmPoolList.Items { 991 + if pool.Status.Phase != hsmv1alpha1.HSMPoolPhaseReady { 992 + continue 993 + } 994 + 995 + // Extract device name from pool name (remove "-pool" suffix) 996 + deviceName := strings.TrimSuffix(pool.Name, "-pool") 997 + 998 + if podIPs, err := m.GetAgentPodIPs(ctx, deviceName, namespace); err == nil && len(podIPs) > 0 { 999 + availableDevices = append(availableDevices, deviceName) 1000 + } 1001 + } 1002 + 1003 + if len(availableDevices) == 0 { 1004 + return nil, fmt.Errorf("no available HSM agents found") 1005 + } 1006 + 1007 + return availableDevices, nil 978 1008 } 979 1009 980 1010 func int32Ptr(i int32) *int32 {
-49
internal/api/proxy_client.go
··· 57 57 err error 58 58 } 59 59 60 - // ListSecretsResponse represents the response for listing secrets 61 - type ListSecretsResponse struct { 62 - Secrets []string `json:"secrets"` 63 - Count int `json:"count"` 64 - Prefix string `json:"prefix,omitempty"` 65 - } 66 - 67 - // ReadSecretResponse represents the response for reading a secret 68 - type ReadSecretResponse struct { 69 - Path string `json:"path"` 70 - Data map[string][]byte `json:"data"` 71 - } 72 - 73 - // WriteSecretResponse represents the response for writing a secret 74 - type WriteSecretResponse struct { 75 - Path string `json:"path"` 76 - Keys int `json:"keys"` 77 - } 78 - 79 - // DeleteSecretResponse represents the response for deleting a secret 80 - type DeleteSecretResponse struct { 81 - Path string `json:"path"` 82 - Devices int `json:"devices"` 83 - DeviceResults map[string]any `json:"deviceResults"` 84 - Warnings []string `json:"warnings,omitempty"` 85 - } 86 - 87 - // ReadMetadataResponse represents the response for reading metadata 88 - type ReadMetadataResponse struct { 89 - Path string `json:"path"` 90 - Metadata *hsm.SecretMetadata `json:"metadata"` 91 - } 92 - 93 - // GetChecksumResponse represents the response for getting a checksum 94 - type GetChecksumResponse struct { 95 - Path string `json:"path"` 96 - Checksum string `json:"checksum"` 97 - } 98 - 99 - type IsConnectedResponse struct { 100 - Devices map[string]bool `json:"devices"` 101 - TotalDevices int `json:"totalDevices"` 102 - } 103 - 104 - // GetInfoResponse represents the response for getting HSM info 105 - type GetInfoResponse struct { 106 - DeviceInfos map[string]*hsm.HSMInfo `json:"deviceInfos"` // deviceName -> HSMInfo 107 - } 108 - 109 60 // ProxyClient handles HTTP requests and proxies them to gRPC clients 110 61 // It has methods that match the HTTP endpoints and handle the full request/response cycle 111 62 type ProxyClient struct {
+1 -28
internal/api/server.go
··· 20 20 "context" 21 21 "fmt" 22 22 "net/http" 23 - "strings" 24 23 "time" 25 24 26 25 "github.com/gin-gonic/gin" ··· 28 27 "github.com/go-playground/validator/v10" 29 28 "sigs.k8s.io/controller-runtime/pkg/client" 30 29 31 - hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1" 32 30 "github.com/evanjarrett/hsm-secrets-operator/internal/agent" 33 31 "github.com/evanjarrett/hsm-secrets-operator/internal/hsm" 34 32 ) ··· 172 170 return nil, fmt.Errorf("agent manager not available") 173 171 } 174 172 175 - // List all HSMPools to find all with active agents 176 - var hsmPoolList hsmv1alpha1.HSMPoolList 177 - if err := s.client.List(ctx, &hsmPoolList, client.InNamespace(namespace)); err != nil { 178 - return nil, fmt.Errorf("failed to list HSM pools: %w", err) 179 - } 180 - 181 - var availableDevices []string 182 - // Check all pools that have active agents 183 - for _, pool := range hsmPoolList.Items { 184 - if pool.Status.Phase != hsmv1alpha1.HSMPoolPhaseReady { 185 - continue 186 - } 187 - 188 - // Extract device name from pool name (remove "-pool" suffix) 189 - deviceName := strings.TrimSuffix(pool.Name, "-pool") 190 - 191 - if podIPs, err := s.agentManager.GetAgentPodIPs(ctx, deviceName, namespace); err == nil && len(podIPs) > 0 { 192 - availableDevices = append(availableDevices, deviceName) 193 - } 194 - } 195 - 196 - if len(availableDevices) == 0 { 197 - return nil, fmt.Errorf("no available HSM agents found") 198 - } 199 - 200 - return availableDevices, nil 173 + return s.agentManager.GetAvailableDevices(ctx, namespace) 201 174 } 202 175 203 176 // createGRPCClient creates a gRPC client for the specified device using AgentManager
+51
internal/api/types.go
··· 18 18 19 19 import ( 20 20 "time" 21 + 22 + "github.com/evanjarrett/hsm-secrets-operator/internal/hsm" 21 23 ) 22 24 23 25 // SecretFormat defines the format of the secret data ··· 190 192 // Timestamp is when the health check was performed 191 193 Timestamp time.Time `json:"timestamp"` 192 194 } 195 + 196 + // ListSecretsResponse represents the response for listing secrets 197 + type ListSecretsResponse struct { 198 + Secrets []string `json:"secrets"` 199 + Count int `json:"count"` 200 + Prefix string `json:"prefix,omitempty"` 201 + } 202 + 203 + // ReadSecretResponse represents the response for reading a secret 204 + type ReadSecretResponse struct { 205 + Path string `json:"path"` 206 + Data map[string][]byte `json:"data"` 207 + } 208 + 209 + // WriteSecretResponse represents the response for writing a secret 210 + type WriteSecretResponse struct { 211 + Path string `json:"path"` 212 + Keys int `json:"keys"` 213 + } 214 + 215 + // DeleteSecretResponse represents the response for deleting a secret 216 + type DeleteSecretResponse struct { 217 + Path string `json:"path"` 218 + Devices int `json:"devices"` 219 + DeviceResults map[string]any `json:"deviceResults"` 220 + Warnings []string `json:"warnings,omitempty"` 221 + } 222 + 223 + // ReadMetadataResponse represents the response for reading metadata 224 + type ReadMetadataResponse struct { 225 + Path string `json:"path"` 226 + Metadata *hsm.SecretMetadata `json:"metadata"` 227 + } 228 + 229 + // GetChecksumResponse represents the response for getting a checksum 230 + type GetChecksumResponse struct { 231 + Path string `json:"path"` 232 + Checksum string `json:"checksum"` 233 + } 234 + 235 + type IsConnectedResponse struct { 236 + Devices map[string]bool `json:"devices"` 237 + TotalDevices int `json:"totalDevices"` 238 + } 239 + 240 + // GetInfoResponse represents the response for getting HSM info 241 + type GetInfoResponse struct { 242 + DeviceInfos map[string]*hsm.HSMInfo `json:"deviceInfos"` // deviceName -> HSMInfo 243 + }
+46 -332
internal/controller/hsmsecret_controller.go
··· 20 20 "context" 21 21 "fmt" 22 22 "maps" 23 + "slices" 23 24 "strings" 24 25 "time" 25 26 ··· 120 121 return ctrl.Result{}, nil 121 122 } 122 123 123 - // Find target HSM devices and ensure agents are running 124 - deviceClients, err := r.ensureHSMAgents(ctx, &hsmSecret) 125 - if err != nil { 126 - logger.Error(err, "Failed to ensure HSM agents") 127 - return ctrl.Result{RequeueAfter: time.Minute * 5}, nil 128 - } 129 - defer func() { 130 - if err := deviceClients.Close(); err != nil { 131 - logger.Error(err, "Failed to close device clients") 132 - } 133 - }() 134 - 135 - // Check that we have at least one connected client 136 - if len(deviceClients.Clients) == 0 { 137 - logger.Error(fmt.Errorf("no HSM agents available"), "No HSM agents connected") 138 - return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 139 - } 140 - 141 - // Validate all clients are connected 142 - for i, hsmClient := range deviceClients.Clients { 143 - if hsmClient == nil || !hsmClient.IsConnected() { 144 - logger.Error(fmt.Errorf("HSM agent not available"), "HSM agent not connected", "device", deviceClients.Devices[i].Name) 145 - return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 146 - } 147 - } 148 - 149 124 // Handle deletion 150 125 if hsmSecret.DeletionTimestamp != nil { 151 126 return ctrl.Result{}, r.reconcileDelete(ctx, &hsmSecret) ··· 161 136 return ctrl.Result{Requeue: true}, nil 162 137 } 163 138 164 - // Reconcile the HSMSecret across all available devices 165 - result, err := r.reconcileSecret(ctx, &hsmSecret, deviceClients) 139 + // Find available devices via AgentManager 140 + devices, err := r.AgentManager.GetAvailableDevices(ctx, r.OperatorNamespace) 166 141 if err != nil { 167 - logger.Error(err, "Failed to reconcile HSMSecret") 168 - r.updateStatus(ctx, &hsmSecret, hsmv1alpha1.SyncStatusError, err.Error()) 142 + logger.Error(err, "Failed to get available devices") 143 + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 144 + } 145 + if len(devices) == 0 { 146 + logger.Info("No HSM devices available") 147 + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 169 148 } 170 149 171 - return result, err 172 - } 173 - 174 - // ensureHSMAgents finds all HSM devices and ensures agents are running for each 175 - func (r *HSMSecretReconciler) ensureHSMAgents(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret) (*HSMDeviceClients, error) { 176 - logger := log.FromContext(ctx) 150 + // Connect to first available device 151 + deviceName := devices[0] 152 + grpcClient, err := r.AgentManager.CreateGRPCClient(ctx, deviceName, hsmSecret.Namespace, logger) 153 + if err != nil { 154 + logger.Error(err, "Failed to create gRPC client", "device", deviceName) 155 + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 156 + } 157 + defer func() { 158 + if err := grpcClient.Close(); err != nil { 159 + logger.Error(err, "Failed to close gRPC client") 160 + } 161 + }() 177 162 178 - // Find all appropriate HSM devices 179 - hsmDevices, err := r.findAllHSMDevices(ctx) 163 + // Check if secret exists 164 + secrets, err := grpcClient.ListSecrets(ctx, "") 180 165 if err != nil { 181 - return nil, fmt.Errorf("failed to find HSM devices for secret: %w", err) 166 + logger.Error(err, "Failed to list secrets") 167 + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 182 168 } 183 169 184 - if r.AgentManager == nil { 185 - return nil, fmt.Errorf("agent manager not configured") 170 + if !slices.Contains(secrets, hsmSecret.Name) { 171 + logger.Info("Secret not found in HSM", "secret", hsmSecret.Name) 172 + return ctrl.Result{RequeueAfter: time.Minute * 5}, nil 186 173 } 187 174 188 - deviceClients := &HSMDeviceClients{ 189 - Devices: hsmDevices, 190 - Clients: make([]hsm.Client, 0, len(hsmDevices)), 175 + // Read secret data and metadata 176 + hsmData, err := grpcClient.ReadSecret(ctx, hsmSecret.Name) 177 + if err != nil { 178 + logger.Error(err, "Failed to read secret from HSM") 179 + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil 191 180 } 192 181 193 - // Ensure agent pods are running for all devices and create clients 194 - for _, hsmDevice := range hsmDevices { 195 - // Get the HSMPool for this device 196 - poolName := hsmDevice.Name + "-pool" 197 - var hsmPool hsmv1alpha1.HSMPool 198 - if err := r.Get(ctx, types.NamespacedName{ 199 - Name: poolName, 200 - Namespace: hsmDevice.Namespace, 201 - }, &hsmPool); err != nil { 202 - // Clean up any successful connections before returning error 203 - if err := deviceClients.Close(); err != nil { 204 - logger.Error(err, "Failed to close device clients during cleanup") 205 - } 206 - return nil, fmt.Errorf("failed to get HSMPool %s for device %s: %w", poolName, hsmDevice.Name, err) 207 - } 208 - 209 - // EnsureAgent ensures agents for all devices in the pool 210 - err = r.AgentManager.EnsureAgent(ctx, &hsmPool) 211 - if err != nil { 212 - // Clean up any successful connections before returning error 213 - if err := deviceClients.Close(); err != nil { 214 - logger.Error(err, "Failed to close device clients during cleanup") 215 - } 216 - return nil, fmt.Errorf("failed to ensure HSM agent for device %s: %w", hsmDevice.Name, err) 217 - } 182 + hsmMetadata, err := grpcClient.ReadMetadata(ctx, hsmSecret.Name) 183 + if err != nil { 184 + logger.V(1).Info("Failed to read metadata", "error", err) 185 + hsmMetadata = nil // Continue without metadata 186 + } 218 187 219 - // Create gRPC client using agent manager's direct pod connections 220 - agentClient, err := r.AgentManager.CreateGRPCClient(ctx, hsmDevice.Name, hsmSecret.Namespace, logger) 221 - if err != nil { 222 - // Clean up any successful connections before returning error 223 - if err := deviceClients.Close(); err != nil { 224 - logger.Error(err, "Failed to close device clients during cleanup") 225 - } 226 - return nil, fmt.Errorf("failed to create gRPC client for device %s: %w", hsmDevice.Name, err) 227 - } 228 - 229 - // Test connection 230 - if !agentClient.IsConnected() { 231 - logger.Info("Waiting for HSM agent to be ready", "device", hsmDevice.Name) 232 - time.Sleep(5 * time.Second) 233 - 234 - // Test again 235 - if !agentClient.IsConnected() { 236 - if err := agentClient.Close(); err != nil { 237 - logger.Error(err, "Failed to close gRPC client after failed connection test") 238 - } 239 - // Clean up any successful connections before returning error 240 - if err := deviceClients.Close(); err != nil { 241 - logger.Error(err, "Failed to close device clients during cleanup") 242 - } 243 - return nil, fmt.Errorf("HSM agent not ready after waiting for device %s", hsmDevice.Name) 244 - } 245 - } 246 - 247 - deviceClients.Clients = append(deviceClients.Clients, agentClient) 188 + // Reconcile the HSMSecret with the data from HSM 189 + result, err := r.reconcileSecret(ctx, &hsmSecret, hsmData, hsmMetadata) 190 + if err != nil { 191 + logger.Error(err, "Failed to reconcile HSMSecret") 192 + r.updateStatus(ctx, &hsmSecret, hsmv1alpha1.SyncStatusError, err.Error()) 248 193 } 249 194 250 - return deviceClients, nil 195 + return result, err 251 196 } 252 197 253 - // reconcileSecret handles HSM secret reconciliation across all available devices 254 - func (r *HSMSecretReconciler) reconcileSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, deviceClients *HSMDeviceClients) (ctrl.Result, error) { 255 - logger := log.FromContext(ctx) 256 - 198 + // reconcileSecret handles HSM secret reconciliation with data from HSM 199 + func (r *HSMSecretReconciler) reconcileSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, hsmData hsm.SecretData, hsmMetadata *hsm.SecretMetadata) (ctrl.Result, error) { 257 200 // Set default values 258 201 secretName := hsmSecret.Spec.SecretName 259 202 if secretName == "" { ··· 265 208 syncInterval = DefaultSyncInterval 266 209 } 267 210 268 - // Read from all devices (handles both single and multi-device scenarios) 269 - if len(deviceClients.Devices) > 1 { 270 - logger.Info("Multi-device setup detected, checking for consistency", "deviceCount", len(deviceClients.Devices)) 271 - } else { 272 - logger.V(1).Info("Single device setup", "deviceCount", len(deviceClients.Devices)) 273 - } 274 - 275 - deviceInfos, primaryDevice, err := r.readFromAllDevices(ctx, hsmSecret, deviceClients) 276 - if err != nil { 277 - logger.Error(err, "Failed to read from devices") 278 - return ctrl.Result{RequeueAfter: time.Minute * 2}, err 279 - } 280 - 281 - // Check for inconsistencies and sync if needed (only matters for multi-device) 282 - if len(deviceClients.Devices) > 1 && r.detectInconsistencies(deviceInfos) { 283 - logger.Info("Inconsistency detected between devices, performing sync", "primaryDevice", primaryDevice) 284 - 285 - if err := r.syncAcrossDevices(ctx, hsmSecret, deviceClients, primaryDevice, deviceInfos[primaryDevice]); err != nil { 286 - logger.Error(err, "Failed to sync across devices") 287 - return ctrl.Result{RequeueAfter: time.Minute * 2}, err 288 - } 289 - logger.Info("Successfully synced secret across all devices") 290 - } 291 - 292 - // Use the primary device data for Kubernetes secret 293 - hsmData := deviceInfos[primaryDevice].Data 294 - hsmMetadata := deviceInfos[primaryDevice].Metadata 295 - 296 211 return r.updateKubernetesSecret(ctx, hsmSecret, secretName, hsmData, hsmMetadata, syncInterval) 297 212 } 298 213 299 - // readFromAllDevices reads the secret from all devices with version information 300 - func (r *HSMSecretReconciler) readFromAllDevices(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, deviceClients *HSMDeviceClients) (map[string]*DeviceInfo, string, error) { 301 - logger := log.FromContext(ctx) 302 - deviceInfos := make(map[string]*DeviceInfo) 303 - 304 - for i, hsmClient := range deviceClients.Clients { 305 - deviceName := deviceClients.Devices[i].Name 306 - 307 - data, err := hsmClient.ReadSecret(ctx, hsmSecret.Name) 308 - if err != nil { 309 - logger.V(1).Info("Failed to read from device", "device", deviceName, "error", err) 310 - // Continue with other devices - this might be a new device without the secret 311 - continue 312 - } 313 - 314 - // Read metadata to get version information 315 - metadata, err := hsmClient.ReadMetadata(ctx, hsmSecret.Name) 316 - if err != nil { 317 - logger.V(1).Info("Failed to read metadata from device", "device", deviceName, "error", err) 318 - metadata = nil 319 - } 320 - 321 - // Extract version from metadata 322 - var version int64 323 - if metadata != nil && metadata.Labels != nil { 324 - if versionStr, exists := metadata.Labels["sync.version"]; exists { 325 - if parsedVersion, parseErr := r.parseVersion(versionStr); parseErr == nil { 326 - version = parsedVersion 327 - } 328 - } 329 - } 330 - 331 - deviceInfos[deviceName] = &DeviceInfo{ 332 - Data: data, 333 - Metadata: metadata, 334 - Version: version, 335 - Checksum: hsm.CalculateChecksum(data), 336 - Timestamp: time.Now(), 337 - } 338 - } 339 - 340 - if len(deviceInfos) == 0 { 341 - return nil, "", fmt.Errorf("no devices contain the secret %s", hsmSecret.Name) 342 - } 343 - 344 - // Select primary device based on version and HSMSecret status 345 - primaryDevice := r.selectPrimaryDevice(deviceInfos, hsmSecret) 346 - 347 - return deviceInfos, primaryDevice, nil 348 - } 349 - 350 - // parseVersion parses version string from metadata 351 - func (r *HSMSecretReconciler) parseVersion(versionStr string) (int64, error) { 352 - var version int64 353 - _, err := fmt.Sscanf(versionStr, "%d", &version) 354 - return version, err 355 - } 356 - 357 - // selectPrimaryDevice chooses the primary device based on version and HSMSecret status 358 - func (r *HSMSecretReconciler) selectPrimaryDevice(deviceInfos map[string]*DeviceInfo, hsmSecret *hsmv1alpha1.HSMSecret) string { 359 - // Check if there's already a designated primary in the status that's still available 360 - if hsmSecret.Status.PrimaryDevice != "" { 361 - if info, exists := deviceInfos[hsmSecret.Status.PrimaryDevice]; exists && info != nil { 362 - return hsmSecret.Status.PrimaryDevice 363 - } 364 - } 365 - 366 - // Find device with highest version number 367 - var bestDevice string 368 - var highestVersion int64 = -1 369 - var mostRecentTime time.Time 370 - 371 - for deviceName, info := range deviceInfos { 372 - // Prefer higher version numbers 373 - if info.Version > highestVersion { 374 - highestVersion = info.Version 375 - bestDevice = deviceName 376 - mostRecentTime = info.Timestamp 377 - } else if info.Version == highestVersion && info.Timestamp.After(mostRecentTime) { 378 - // If versions are equal, prefer more recent timestamp 379 - bestDevice = deviceName 380 - mostRecentTime = info.Timestamp 381 - } 382 - } 383 - 384 - return bestDevice 385 - } 386 - 387 - // detectInconsistencies checks if devices have different versions of the secret 388 - func (r *HSMSecretReconciler) detectInconsistencies(deviceInfos map[string]*DeviceInfo) bool { 389 - if len(deviceInfos) <= 1 { 390 - return false 391 - } 392 - 393 - checksums := make(map[string]int) 394 - for _, info := range deviceInfos { 395 - checksums[info.Checksum]++ 396 - } 397 - 398 - // Inconsistency if we have more than one unique checksum 399 - return len(checksums) > 1 400 - } 401 - 402 - // syncAcrossDevices copies the primary device's secret to all other devices with proper versioning 403 - func (r *HSMSecretReconciler) syncAcrossDevices(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, deviceClients *HSMDeviceClients, primaryDevice string, primaryInfo *DeviceInfo) error { 404 - logger := log.FromContext(ctx) 405 - 406 - for i, hsmClient := range deviceClients.Clients { 407 - deviceName := deviceClients.Devices[i].Name 408 - 409 - // Skip the primary device 410 - if deviceName == primaryDevice { 411 - continue 412 - } 413 - 414 - logger.Info("Syncing secret to device", "device", deviceName, "from", primaryDevice) 415 - 416 - // Create metadata with updated version and sync information 417 - newVersion := time.Now().Unix() 418 - metadata := &hsm.SecretMetadata{ 419 - Labels: map[string]string{ 420 - "sync.version": fmt.Sprintf("%d", newVersion), 421 - "sync.timestamp": time.Now().Format(time.RFC3339), 422 - }, 423 - } 424 - 425 - // Copy over other metadata if it exists 426 - if primaryInfo.Metadata != nil { 427 - if metadata.Labels == nil { 428 - metadata.Labels = make(map[string]string) 429 - } 430 - if primaryInfo.Metadata.Description != "" { 431 - metadata.Description = primaryInfo.Metadata.Description 432 - } 433 - if primaryInfo.Metadata.Format != "" { 434 - metadata.Format = primaryInfo.Metadata.Format 435 - } 436 - if primaryInfo.Metadata.DataType != "" { 437 - metadata.DataType = primaryInfo.Metadata.DataType 438 - } 439 - if primaryInfo.Metadata.Source != "" { 440 - metadata.Source = primaryInfo.Metadata.Source 441 - } 442 - // Copy non-sync labels 443 - for key, value := range primaryInfo.Metadata.Labels { 444 - if !strings.HasPrefix(key, "sync.") { 445 - metadata.Labels[key] = value 446 - } 447 - } 448 - } 449 - 450 - // Write the primary device's data with metadata to this device 451 - if err := hsmClient.WriteSecretWithMetadata(ctx, hsmSecret.Name, primaryInfo.Data, metadata); err != nil { 452 - logger.Error(err, "Failed to sync secret to device", "device", deviceName) 453 - return fmt.Errorf("failed to sync to device %s: %w", deviceName, err) 454 - } 455 - 456 - logger.Info("Successfully synced secret to device", "device", deviceName, "version", newVersion) 457 - } 458 - 459 - return nil 460 - } 461 - 462 214 // updateKubernetesSecret updates the Kubernetes Secret with the given data 463 215 func (r *HSMSecretReconciler) updateKubernetesSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret, secretName string, hsmData hsm.SecretData, hsmMetadata *hsm.SecretMetadata, syncInterval int32) (ctrl.Result, error) { 464 216 logger := log.FromContext(ctx) ··· 757 509 if !found { 758 510 hsmSecret.Status.Conditions = append(hsmSecret.Status.Conditions, condition) 759 511 } 760 - } 761 - 762 - // findAllHSMDevices finds all HSMDevices with ready HSMPools 763 - // Note: HSMDevices are managed in the operator namespace, not the HSMSecret's namespace 764 - func (r *HSMSecretReconciler) findAllHSMDevices(ctx context.Context) ([]*hsmv1alpha1.HSMDevice, error) { 765 - // List HSMDevices in this operator's namespace (where operator infrastructure is contained) 766 - var hsmDeviceList hsmv1alpha1.HSMDeviceList 767 - if err := r.List(ctx, &hsmDeviceList, client.InNamespace(r.OperatorNamespace)); err != nil { 768 - return nil, fmt.Errorf("failed to list HSM devices: %w", err) 769 - } 770 - 771 - var readyDevices []*hsmv1alpha1.HSMDevice 772 - 773 - // Look for devices with associated HSMPools that are ready with available devices 774 - for _, device := range hsmDeviceList.Items { 775 - // Check the HSMPool for this device 776 - poolName := device.Name + "-pool" 777 - pool := &hsmv1alpha1.HSMPool{} 778 - 779 - err := r.Get(ctx, client.ObjectKey{ 780 - Name: poolName, 781 - Namespace: device.Namespace, 782 - }, pool) 783 - 784 - if err == nil && pool.Status.Phase == hsmv1alpha1.HSMPoolPhaseReady && 785 - len(pool.Status.AggregatedDevices) > 0 { 786 - 787 - // This is a suitable device for HSM operations 788 - deviceCopy := device // Create a copy to avoid issues with loop variable 789 - readyDevices = append(readyDevices, &deviceCopy) 790 - } 791 - } 792 - 793 - if len(readyDevices) == 0 { 794 - return nil, fmt.Errorf("no suitable HSM devices found in ready state") 795 - } 796 - 797 - return readyDevices, nil 798 512 } 799 513 800 514 // shouldHandleSecret determines if this operator instance should handle the given HSMSecret