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.

update hsmsecrets to have a parentref that point back to the operator that manages them. this allows hsmsecrets to be deployed outside of the operator namespace

+557 -16
+30
api/v1alpha1/hsmsecret_types.go
··· 24 24 // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 25 // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 26 27 + // ParentReference identifies an API object (typically an HSM operator instance) 28 + // to which the HSMSecret should be associated. 29 + type ParentReference struct { 30 + // Name is the name of the parent resource. 31 + Name string `json:"name"` 32 + 33 + // Namespace is the namespace of the parent resource. 34 + // This must be provided when the parent is in a different namespace 35 + // from the HSMSecret. 36 + // +optional 37 + Namespace *string `json:"namespace,omitempty"` 38 + 39 + // Group is the API group of the parent resource. 40 + // Defaults to "apps" (for Deployment resources). 41 + // +kubebuilder:default="apps" 42 + // +optional 43 + Group *string `json:"group,omitempty"` 44 + 45 + // Kind is the kind of the parent resource. 46 + // Defaults to "Deployment". 47 + // +kubebuilder:default="Deployment" 48 + // +optional 49 + Kind *string `json:"kind,omitempty"` 50 + } 51 + 27 52 // HSMSecretSpec defines the desired state of HSMSecret. 28 53 type HSMSecretSpec struct { 54 + // ParentRef identifies the HSM operator instance that should handle this HSMSecret. 55 + // HSMSecrets without a ParentRef are ignored by all operators. 56 + // +optional 57 + ParentRef *ParentReference `json:"parentRef,omitempty"` 58 + 29 59 // SecretName is the name of the Kubernetes Secret object to create/update 30 60 // Defaults to the HSMSecret name if not specified 31 61 // +optional
+36 -1
api/v1alpha1/zz_generated.deepcopy.go
··· 346 346 *out = *in 347 347 out.TypeMeta = in.TypeMeta 348 348 in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 349 - out.Spec = in.Spec 349 + in.Spec.DeepCopyInto(&out.Spec) 350 350 in.Status.DeepCopyInto(&out.Status) 351 351 } 352 352 ··· 403 403 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 404 404 func (in *HSMSecretSpec) DeepCopyInto(out *HSMSecretSpec) { 405 405 *out = *in 406 + if in.ParentRef != nil { 407 + in, out := &in.ParentRef, &out.ParentRef 408 + *out = new(ParentReference) 409 + (*in).DeepCopyInto(*out) 410 + } 406 411 } 407 412 408 413 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HSMSecretSpec. ··· 518 523 return nil 519 524 } 520 525 out := new(PKCS11Config) 526 + in.DeepCopyInto(out) 527 + return out 528 + } 529 + 530 + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 531 + func (in *ParentReference) DeepCopyInto(out *ParentReference) { 532 + *out = *in 533 + if in.Namespace != nil { 534 + in, out := &in.Namespace, &out.Namespace 535 + *out = new(string) 536 + **out = **in 537 + } 538 + if in.Group != nil { 539 + in, out := &in.Group, &out.Group 540 + *out = new(string) 541 + **out = **in 542 + } 543 + if in.Kind != nil { 544 + in, out := &in.Kind, &out.Kind 545 + *out = new(string) 546 + **out = **in 547 + } 548 + } 549 + 550 + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentReference. 551 + func (in *ParentReference) DeepCopy() *ParentReference { 552 + if in == nil { 553 + return nil 554 + } 555 + out := new(ParentReference) 521 556 in.DeepCopyInto(out) 522 557 return out 523 558 }
+29
config/crd/bases/hsm.j5t.io_hsmsecrets.yaml
··· 59 59 description: AutoSync enables bidirectional synchronization between 60 60 HSM and Kubernetes Secret 61 61 type: boolean 62 + parentRef: 63 + description: |- 64 + ParentRef identifies the HSM operator instance that should handle this HSMSecret. 65 + HSMSecrets without a ParentRef are ignored by all operators. 66 + properties: 67 + group: 68 + default: apps 69 + description: |- 70 + Group is the API group of the parent resource. 71 + Defaults to "apps" (for Deployment resources). 72 + type: string 73 + kind: 74 + default: Deployment 75 + description: |- 76 + Kind is the kind of the parent resource. 77 + Defaults to "Deployment". 78 + type: string 79 + name: 80 + description: Name is the name of the parent resource. 81 + type: string 82 + namespace: 83 + description: |- 84 + Namespace is the namespace of the parent resource. 85 + This must be provided when the parent is in a different namespace 86 + from the HSMSecret. 87 + type: string 88 + required: 89 + - name 90 + type: object 62 91 secretName: 63 92 description: |- 64 93 SecretName is the name of the Kubernetes Secret object to create/update
+75
config/samples/README.md
··· 1 + # HSMSecret Cross-Namespace Support 2 + 3 + This directory contains sample HSMSecret manifests demonstrating cross-namespace functionality. 4 + 5 + ## ParentRef-Based Operator Association 6 + 7 + When multiple HSM operator instances are deployed in a cluster, HSMSecrets use `parentRef` to specify which operator should handle them: 8 + 9 + ```yaml 10 + apiVersion: hsm.j5t.io/v1alpha1 11 + kind: HSMSecret 12 + metadata: 13 + name: my-secret 14 + namespace: production 15 + spec: 16 + parentRef: 17 + name: controller-manager 18 + namespace: hsm-operator-system 19 + # ... rest of spec 20 + ``` 21 + 22 + ## Behavior 23 + 24 + - **With parentRef**: Only the operator with matching name and namespace will handle the HSMSecret 25 + - **Without parentRef**: HSMSecret is ignored by all operators (explicit association required) 26 + 27 + ## Architecture 28 + 29 + - **HSMSecrets**: Can be created in any namespace 30 + - **Kubernetes Secrets**: Created in the same namespace as their HSMSecret 31 + - **Operator Infrastructure**: HSMDevices, HSMPools, agents remain in the operator's namespace 32 + - **RBAC**: ClusterRole provides cluster-wide permissions 33 + 34 + ## Helm Integration 35 + 36 + When deploying via Helm, the `parentRef` is automatically added to HSMSecrets: 37 + 38 + ```yaml 39 + # In Helm values.yaml 40 + hsmsecret: 41 + enabled: true 42 + secrets: 43 + - name: "database-credentials" 44 + namespace: "production" 45 + secretName: "db-secrets" 46 + syncInterval: 300 47 + autoSync: true 48 + - name: "api-keys" 49 + namespace: "development" 50 + secretName: "third-party-keys" 51 + syncInterval: 60 52 + ``` 53 + 54 + This creates HSMSecrets with automatically generated `parentRef`: 55 + 56 + ```yaml 57 + apiVersion: hsm.j5t.io/v1alpha1 58 + kind: HSMSecret 59 + metadata: 60 + name: database-credentials 61 + namespace: production 62 + spec: 63 + parentRef: 64 + name: my-release-hsm-secrets-operator-controller-manager 65 + namespace: my-operator-namespace 66 + secretName: db-secrets 67 + syncInterval: 300 68 + autoSync: true 69 + ``` 70 + 71 + **Benefits**: 72 + - No manual parentRef configuration needed 73 + - Automatic association with the deploying Helm release 74 + - Multi-tenant support for multiple operator deployments 75 + - Cross-namespace secret management with explicit operator ownership
+6
config/samples/hsm_v1alpha1_hsmsecret.yaml
··· 9 9 spec: 10 10 # HSM path is automatically set to the metadata.name (hsmsecret-sample) 11 11 12 + # ParentRef identifies which operator instance should handle this HSMSecret 13 + # Comment out to ignore this HSMSecret (no operator will process it) 14 + parentRef: 15 + name: controller-manager 16 + namespace: hsm-secrets-operator-system 17 + 12 18 # Name of the Kubernetes Secret to create (defaults to HSMSecret name if not specified) 13 19 secretName: "database-credentials" 14 20
+57
config/samples/hsm_v1alpha1_hsmsecret_crossnamespace.yaml
··· 1 + apiVersion: hsm.j5t.io/v1alpha1 2 + kind: HSMSecret 3 + metadata: 4 + labels: 5 + app.kubernetes.io/name: hsm-secrets-operator 6 + app.kubernetes.io/managed-by: kustomize 7 + name: app-database-secret 8 + namespace: production # This HSMSecret is in the 'production' namespace 9 + spec: 10 + # HSM path is automatically set to the metadata.name (app-database-secret) 11 + 12 + # ParentRef identifies which operator instance should handle this HSMSecret 13 + parentRef: 14 + name: controller-manager 15 + namespace: hsm-secrets-operator-system 16 + 17 + # Name of the Kubernetes Secret to create (defaults to HSMSecret name if not specified) 18 + secretName: "database-credentials" 19 + 20 + # Enable automatic synchronization between HSM and Kubernetes Secret 21 + autoSync: true 22 + 23 + # Synchronization interval in seconds (default: 300) 24 + syncInterval: 60 25 + 26 + # Type of Kubernetes Secret to create (default: Opaque) 27 + secretType: Opaque 28 + 29 + --- 30 + # Another HSMSecret in a different namespace to demonstrate cross-namespace functionality 31 + apiVersion: hsm.j5t.io/v1alpha1 32 + kind: HSMSecret 33 + metadata: 34 + labels: 35 + app.kubernetes.io/name: hsm-secrets-operator 36 + app.kubernetes.io/managed-by: kustomize 37 + name: api-keys 38 + namespace: development # This HSMSecret is in the 'development' namespace 39 + spec: 40 + # HSM path is automatically set to the metadata.name (api-keys) 41 + 42 + # ParentRef identifies which operator instance should handle this HSMSecret 43 + parentRef: 44 + name: controller-manager 45 + namespace: hsm-secrets-operator-system 46 + 47 + # Name of the Kubernetes Secret to create (defaults to HSMSecret name if not specified) 48 + secretName: "third-party-api-keys" 49 + 50 + # Enable automatic synchronization between HSM and Kubernetes Secret 51 + autoSync: true 52 + 53 + # Synchronization interval in seconds 54 + syncInterval: 30 55 + 56 + # Type of Kubernetes Secret to create 57 + secretType: Opaque
+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.9 6 - appVersion: v0.5.9 5 + version: 0.5.10 6 + appVersion: v0.5.10 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:
+81 -2
helm/hsm-secrets-operator/crds/hsm.j5t.io_hsmsecrets.yaml
··· 59 59 description: AutoSync enables bidirectional synchronization between 60 60 HSM and Kubernetes Secret 61 61 type: boolean 62 + parentRef: 63 + description: |- 64 + ParentRef identifies the HSM operator instance that should handle this HSMSecret. 65 + HSMSecrets without a ParentRef are ignored by all operators. 66 + properties: 67 + group: 68 + default: apps 69 + description: |- 70 + Group is the API group of the parent resource. 71 + Defaults to "apps" (for Deployment resources). 72 + type: string 73 + kind: 74 + default: Deployment 75 + description: |- 76 + Kind is the kind of the parent resource. 77 + Defaults to "Deployment". 78 + type: string 79 + name: 80 + description: Name is the name of the parent resource. 81 + type: string 82 + namespace: 83 + description: |- 84 + Namespace is the namespace of the parent resource. 85 + This must be provided when the parent is in a different namespace 86 + from the HSMSecret. 87 + type: string 88 + required: 89 + - name 90 + type: object 62 91 secretName: 63 92 description: |- 64 93 SecretName is the name of the Kubernetes Secret object to create/update ··· 70 99 create 71 100 type: string 72 101 syncInterval: 73 - default: 300 102 + default: 30 74 103 description: |- 75 104 SyncInterval defines how often to check for HSM changes (in seconds) 76 105 Only applies when AutoSync is true ··· 138 167 - type 139 168 type: object 140 169 type: array 170 + deviceSyncStatus: 171 + description: DeviceSyncStatus tracks sync status for each HSM device 172 + in mirrored setups 173 + items: 174 + description: HSMDeviceSync tracks synchronization state for a specific 175 + HSM device 176 + properties: 177 + checksum: 178 + description: Checksum is the SHA256 checksum of the data on 179 + this device 180 + type: string 181 + deviceName: 182 + description: DeviceName is the name of the HSM device 183 + type: string 184 + lastError: 185 + description: LastError contains the last error when syncing 186 + with this device 187 + type: string 188 + lastSyncTime: 189 + description: LastSyncTime is the timestamp of the last successful 190 + sync with this device 191 + format: date-time 192 + type: string 193 + online: 194 + description: Online indicates if this device is currently available 195 + type: boolean 196 + status: 197 + description: Status indicates the sync status for this specific 198 + device 199 + type: string 200 + version: 201 + description: |- 202 + Version is a monotonically increasing counter for conflict resolution 203 + Updated each time the secret changes on this device 204 + format: int64 205 + type: integer 206 + required: 207 + - deviceName 208 + type: object 209 + type: array 141 210 hsmChecksum: 142 - description: HSMChecksum is the SHA256 checksum of the HSM data 211 + description: HSMChecksum is the SHA256 checksum of the HSM data (deprecated 212 + - use DeviceSyncStatus) 143 213 type: string 144 214 lastError: 145 215 description: LastError contains the last error message if SyncStatus ··· 149 219 description: LastSyncTime is the timestamp of the last successful 150 220 synchronization 151 221 format: date-time 222 + type: string 223 + primaryDevice: 224 + description: |- 225 + PrimaryDevice indicates which device is currently considered the primary source of truth 226 + Used for conflict resolution in multi-device scenarios 152 227 type: string 153 228 secretChecksum: 154 229 description: SecretChecksum is the SHA256 checksum of the Kubernetes ··· 197 272 type: string 198 273 type: object 199 274 x-kubernetes-map-type: atomic 275 + syncConflict: 276 + description: SyncConflict indicates if there are conflicting versions 277 + across devices 278 + type: boolean 200 279 syncStatus: 201 280 description: SyncStatus indicates the current synchronization status 202 281 type: string
+4
helm/hsm-secrets-operator/templates/resources.yaml
··· 10 10 {{- include "hsm-secrets-operator.labels" $ | nindent 4 }} 11 11 app.kubernetes.io/component: hsmsecret 12 12 spec: 13 + # ParentRef automatically points to this Helm release's operator instance 14 + parentRef: 15 + name: {{ include "hsm-secrets-operator.controllerManagerName" $ }} 16 + namespace: {{ $.Release.Namespace }} 13 17 {{- with .secretName }} 14 18 secretName: {{ . }} 15 19 {{- end }}
+17 -2
helm/hsm-secrets-operator/values.yaml
··· 248 248 # Enable HSMSecret resource creation 249 249 enabled: false 250 250 # List of HSM secrets to manage 251 + # Note: parentRef is automatically added by the Helm chart to associate with this operator instance 251 252 secrets: 252 - - name: "example-secret" 253 - namespace: "default" 253 + - name: "database-credentials" 254 + namespace: "production" 255 + secretName: "db-secrets" 254 256 syncInterval: 300 257 + autoSync: true 258 + - name: "api-keys" 259 + namespace: "development" 260 + secretName: "third-party-keys" 261 + syncInterval: 60 262 + autoSync: true 263 + # Example of additional secrets: 264 + # - name: "tls-certs" 265 + # namespace: "ingress-system" 266 + # secretName: "wildcard-tls" 267 + # secretType: "kubernetes.io/tls" 268 + # syncInterval: 3600 # Sync hourly for certificates 269 + # autoSync: false # Manual sync only 255 270 256 271 # Additional configuration 257 272 config:
+42 -6
internal/controller/hsmsecret_controller.go
··· 49 49 // HSMSecretReconciler reconciles a HSMSecret object 50 50 type HSMSecretReconciler struct { 51 51 client.Client 52 - Scheme *runtime.Scheme 53 - AgentManager *agent.Manager 52 + Scheme *runtime.Scheme 53 + AgentManager *agent.Manager 54 + OperatorNamespace string 55 + OperatorName string 54 56 } 55 57 56 58 // +kubebuilder:rbac:groups=hsm.j5t.io,resources=hsmsecrets,verbs=get;list;watch;create;update;patch;delete ··· 75 77 } 76 78 logger.Error(err, "Failed to get HSMSecret") 77 79 return ctrl.Result{}, err 80 + } 81 + 82 + // Check if this HSMSecret should be handled by this operator instance 83 + if !r.shouldHandleSecret(&hsmSecret) { 84 + logger.V(1).Info("HSMSecret not assigned to this operator instance, skipping", 85 + "secret", hsmSecret.Name, 86 + "namespace", hsmSecret.Namespace, 87 + "operatorName", r.OperatorName, 88 + "operatorNamespace", r.OperatorNamespace) 89 + return ctrl.Result{}, nil 78 90 } 79 91 80 92 // Find target HSM device and ensure agent is running ··· 120 132 logger := log.FromContext(ctx) 121 133 122 134 // Find the appropriate HSM device 123 - hsmDevice, err := r.findHSMDeviceForSecret(ctx, hsmSecret) 135 + hsmDevice, err := r.findHSMDeviceForSecret(ctx) 124 136 if err != nil { 125 137 return nil, nil, fmt.Errorf("failed to find HSM device for secret: %w", err) 126 138 } ··· 503 515 } 504 516 505 517 // findHSMDeviceForSecret finds the HSMDevice that should contain the secret 506 - func (r *HSMSecretReconciler) findHSMDeviceForSecret(ctx context.Context, hsmSecret *hsmv1alpha1.HSMSecret) (*hsmv1alpha1.HSMDevice, error) { 507 - // List all HSMDevices in the same namespace 518 + // Note: HSMDevices are managed in the operator namespace, not the HSMSecret's namespace 519 + func (r *HSMSecretReconciler) findHSMDeviceForSecret(ctx context.Context) (*hsmv1alpha1.HSMDevice, error) { 520 + // List HSMDevices in this operator's namespace (where operator infrastructure is contained) 508 521 var hsmDeviceList hsmv1alpha1.HSMDeviceList 509 - if err := r.List(ctx, &hsmDeviceList, client.InNamespace(hsmSecret.Namespace)); err != nil { 522 + if err := r.List(ctx, &hsmDeviceList, client.InNamespace(r.OperatorNamespace)); err != nil { 510 523 return nil, fmt.Errorf("failed to list HSM devices: %w", err) 511 524 } 512 525 ··· 530 543 } 531 544 532 545 return nil, fmt.Errorf("no suitable HSM device found in ready state") 546 + } 547 + 548 + // shouldHandleSecret determines if this operator instance should handle the given HSMSecret 549 + func (r *HSMSecretReconciler) shouldHandleSecret(hsmSecret *hsmv1alpha1.HSMSecret) bool { 550 + // If no parentRef is present, ignore the secret (explicit association required) 551 + if hsmSecret.Spec.ParentRef == nil { 552 + return false 553 + } 554 + 555 + parentRef := hsmSecret.Spec.ParentRef 556 + 557 + // Check if the parent name matches this operator 558 + if parentRef.Name != r.OperatorName { 559 + return false 560 + } 561 + 562 + // Check namespace match - if parentRef.Namespace is nil, assume same namespace as HSMSecret 563 + expectedNamespace := r.OperatorNamespace 564 + if parentRef.Namespace != nil { 565 + expectedNamespace = *parentRef.Namespace 566 + } 567 + 568 + return expectedNamespace == r.OperatorNamespace 533 569 } 534 570 535 571 // SetupWithManager sets up the controller with the Manager.
+129
internal/controller/hsmsecret_parentref_test.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 + "testing" 21 + 22 + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 + 24 + hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1" 25 + ) 26 + 27 + func stringPtr(s string) *string { 28 + return &s 29 + } 30 + 31 + func TestShouldHandleSecret(t *testing.T) { 32 + reconciler := &HSMSecretReconciler{ 33 + OperatorNamespace: "hsm-operator-system", 34 + OperatorName: "controller-manager", 35 + } 36 + 37 + tests := []struct { 38 + name string 39 + secret *hsmv1alpha1.HSMSecret 40 + expected bool 41 + }{ 42 + { 43 + name: "no parentRef - should not handle (explicit association required)", 44 + secret: &hsmv1alpha1.HSMSecret{ 45 + ObjectMeta: metav1.ObjectMeta{ 46 + Name: "test-secret", 47 + Namespace: "default", 48 + }, 49 + Spec: hsmv1alpha1.HSMSecretSpec{ 50 + // No parentRef 51 + }, 52 + }, 53 + expected: false, 54 + }, 55 + { 56 + name: "matching parentRef - should handle", 57 + secret: &hsmv1alpha1.HSMSecret{ 58 + ObjectMeta: metav1.ObjectMeta{ 59 + Name: "test-secret", 60 + Namespace: "production", 61 + }, 62 + Spec: hsmv1alpha1.HSMSecretSpec{ 63 + ParentRef: &hsmv1alpha1.ParentReference{ 64 + Name: "controller-manager", 65 + Namespace: stringPtr("hsm-operator-system"), 66 + }, 67 + }, 68 + }, 69 + expected: true, 70 + }, 71 + { 72 + name: "different parent name - should not handle", 73 + secret: &hsmv1alpha1.HSMSecret{ 74 + ObjectMeta: metav1.ObjectMeta{ 75 + Name: "test-secret", 76 + Namespace: "production", 77 + }, 78 + Spec: hsmv1alpha1.HSMSecretSpec{ 79 + ParentRef: &hsmv1alpha1.ParentReference{ 80 + Name: "other-operator", 81 + Namespace: stringPtr("hsm-operator-system"), 82 + }, 83 + }, 84 + }, 85 + expected: false, 86 + }, 87 + { 88 + name: "different parent namespace - should not handle", 89 + secret: &hsmv1alpha1.HSMSecret{ 90 + ObjectMeta: metav1.ObjectMeta{ 91 + Name: "test-secret", 92 + Namespace: "production", 93 + }, 94 + Spec: hsmv1alpha1.HSMSecretSpec{ 95 + ParentRef: &hsmv1alpha1.ParentReference{ 96 + Name: "controller-manager", 97 + Namespace: stringPtr("other-operator-system"), 98 + }, 99 + }, 100 + }, 101 + expected: false, 102 + }, 103 + { 104 + name: "parentRef without namespace (should use operator namespace) - should handle", 105 + secret: &hsmv1alpha1.HSMSecret{ 106 + ObjectMeta: metav1.ObjectMeta{ 107 + Name: "test-secret", 108 + Namespace: "production", 109 + }, 110 + Spec: hsmv1alpha1.HSMSecretSpec{ 111 + ParentRef: &hsmv1alpha1.ParentReference{ 112 + Name: "controller-manager", 113 + // Namespace is nil, should default to operator namespace 114 + }, 115 + }, 116 + }, 117 + expected: true, 118 + }, 119 + } 120 + 121 + for _, tt := range tests { 122 + t.Run(tt.name, func(t *testing.T) { 123 + result := reconciler.shouldHandleSecret(tt.secret) 124 + if result != tt.expected { 125 + t.Errorf("shouldHandleSecret() = %v, expected %v", result, tt.expected) 126 + } 127 + }) 128 + } 129 + }
+49 -3
internal/modes/manager/manager.go
··· 19 19 import ( 20 20 "crypto/tls" 21 21 "flag" 22 + "os" 22 23 "path/filepath" 24 + "strings" 23 25 "time" 24 26 25 27 // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) ··· 50 52 func init() { 51 53 utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 52 54 utilruntime.Must(hsmv1alpha1.AddToScheme(scheme)) 55 + } 56 + 57 + // getCurrentNamespace returns the namespace the operator is running in 58 + func getCurrentNamespace() string { 59 + // Try to read namespace from service account mount 60 + if ns, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { 61 + return strings.TrimSpace(string(ns)) 62 + } 63 + 64 + // Fallback to default namespace if we can't determine it 65 + setupLog.Info("Could not determine current namespace, using 'default'") 66 + return "default" 67 + } 68 + 69 + // getOperatorName returns the operator deployment name 70 + // This can be overridden via environment variable or falls back to default 71 + func getOperatorName() string { 72 + // Check if operator name is provided via environment variable 73 + if name := os.Getenv("OPERATOR_NAME"); name != "" { 74 + return name 75 + } 76 + 77 + // Check if deployment name is provided via downward API 78 + if hostname := os.Getenv("HOSTNAME"); hostname != "" { 79 + // Kubernetes deployment pods have hostname like: deployment-name-replicaset-hash-pod-hash 80 + // Extract just the deployment name part 81 + parts := strings.Split(hostname, "-") 82 + if len(parts) >= 2 { 83 + // Return the first two parts as deployment name (e.g., "controller-manager") 84 + return strings.Join(parts[:2], "-") 85 + } 86 + return hostname 87 + } 88 + 89 + // Fallback to default deployment name 90 + setupLog.Info("Could not determine operator name, using 'controller-manager'") 91 + return "controller-manager" 53 92 } 54 93 55 94 // Run starts the manager mode ··· 212 251 // HSM mirroring is now handled by the sync package and HSMSyncReconciler 213 252 // Device discovery is handled by separate discovery daemon 214 253 254 + // Get current operator namespace and name 255 + operatorNamespace := getCurrentNamespace() 256 + operatorName := getOperatorName() 257 + setupLog.Info("Detected operator details", "namespace", operatorNamespace, "name", operatorName) 258 + 215 259 // Agent manager will detect the current namespace automatically 216 260 imageResolver := controller.NewImageResolver(mgr.GetClient()) 217 261 agentManager := agent.NewManager(mgr.GetClient(), "", imageResolver) ··· 237 281 } 238 282 239 283 if err := (&controller.HSMSecretReconciler{ 240 - Client: mgr.GetClient(), 241 - Scheme: mgr.GetScheme(), 242 - AgentManager: agentManager, 284 + Client: mgr.GetClient(), 285 + Scheme: mgr.GetScheme(), 286 + AgentManager: agentManager, 287 + OperatorNamespace: operatorNamespace, 288 + OperatorName: operatorName, 243 289 }).SetupWithManager(mgr); err != nil { 244 290 setupLog.Error(err, "unable to create controller", "controller", "HSMSecret") 245 291 return err