···136136137137.PHONY: manifests
138138manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
139139- CGO_ENABLED=0 $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
139139+ $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
140140141141.PHONY: helm-sync
142142helm-sync: manifests ## Sync generated CRDs from config/ to helm/crds/
···147147148148.PHONY: generate
149149generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
150150- CGO_ENABLED=0 $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
150150+ $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
151151152152.PHONY: fmt
153153fmt: ## Run go fmt against code.
+5-82
internal/discovery/usb.go
···11-//go:build cgo
22-// +build cgo
33-41/*
52Copyright 2025.
63···2219import (
2320 "context"
2421 "fmt"
2222+ "maps"
2523 "strings"
2624 "time"
27252826 "github.com/go-logr/logr"
2929- "github.com/jochenvg/go-udev"
3027 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3128 ctrl "sigs.k8s.io/controller-runtime"
3229···5552// USBDiscoverer handles USB device discovery and monitoring
5653type USBDiscoverer struct {
5754 logger logr.Logger
5858- udev *udev.Udev
5555+ udev *Udev
59566057 // Event monitoring
6161- monitor *udev.Monitor
5858+ monitor *Monitor
6259 eventChannel chan USBEvent
6360 activeSpecs map[string]*hsmv1alpha1.USBDeviceSpec
6461}
···6764func NewUSBDiscoverer() *USBDiscoverer {
6865 logger := ctrl.Log.WithName("usb-discoverer")
69667070- udev := &udev.Udev{}
6767+ udev := &Udev{}
71687269 return &USBDiscoverer{
7370 logger: logger,
···129126 return matchingDevices, nil
130127}
131128132132-// convertUdevDevice converts a go-udev Device to our USBDevice format
133133-func (u *USBDiscoverer) convertUdevDevice(device *udev.Device) *USBDevice {
134134- // Only process actual USB devices, not interfaces
135135- if device.PropertyValue("DEVTYPE") != "usb_device" {
136136- return nil
137137- }
138138-139139- vendorID := device.PropertyValue("ID_VENDOR_ID")
140140- productID := device.PropertyValue("ID_MODEL_ID")
141141-142142- // Skip devices without vendor/product IDs
143143- if vendorID == "" || productID == "" {
144144- return nil
145145- }
146146-147147- return &USBDevice{
148148- VendorID: vendorID,
149149- ProductID: productID,
150150- SerialNumber: device.PropertyValue("ID_SERIAL_SHORT"),
151151- DevicePath: device.Devnode(),
152152- Manufacturer: device.PropertyValue("ID_VENDOR"),
153153- Product: device.PropertyValue("ID_MODEL"),
154154- DeviceInfo: device.Properties(),
155155- }
156156-}
157157-158129// matchesSpec checks if a USB device matches the given specification
159130func (u *USBDiscoverer) matchesSpec(device USBDevice, spec *hsmv1alpha1.USBDeviceSpec) bool {
160131 // Check vendor ID
···241212 }
242213}
243214244244-// handleDeviceEvent processes a single USB device event
245245-func (u *USBDiscoverer) handleDeviceEvent(device *udev.Device) {
246246- action := device.Action()
247247-248248- // Only process add/remove events
249249- if action != "add" && action != "remove" {
250250- return
251251- }
252252-253253- // Convert to USBDevice
254254- usbDev := u.convertUdevDevice(device)
255255- if usbDev == nil {
256256- return
257257- }
258258-259259- u.logger.V(2).Info("Received USB event",
260260- "action", action,
261261- "vendor", usbDev.VendorID,
262262- "product", usbDev.ProductID,
263263- "serial", usbDev.SerialNumber)
264264-265265- // Check which active specs match this device
266266- for hsmDeviceName, spec := range u.activeSpecs {
267267- if u.matchesSpec(*usbDev, spec) {
268268- event := USBEvent{
269269- Action: action,
270270- Device: *usbDev,
271271- Timestamp: time.Now(),
272272- HSMDeviceName: hsmDeviceName,
273273- }
274274-275275- select {
276276- case u.eventChannel <- event:
277277- u.logger.V(1).Info("Sent USB event",
278278- "action", action,
279279- "device", hsmDeviceName,
280280- "vendor", usbDev.VendorID,
281281- "product", usbDev.ProductID,
282282- "serial", usbDev.SerialNumber)
283283- default:
284284- u.logger.Error(nil, "USB event channel full, dropping event", "action", action)
285285- }
286286- }
287287- }
288288-}
289289-290215// AddSpecForMonitoring registers an HSMDevice spec for event monitoring
291216func (u *USBDiscoverer) AddSpecForMonitoring(hsmDeviceName string, spec *hsmv1alpha1.USBDeviceSpec) {
292217 u.activeSpecs[hsmDeviceName] = spec
···337262 }
338263339264 // Add additional device info
340340- for k, v := range usbDevice.DeviceInfo {
341341- device.DeviceInfo[k] = v
342342- }
265265+ maps.Copy(device.DeviceInfo, usbDevice.DeviceInfo)
343266344267 return device
345268}
+102
internal/discovery/usb_cgo.go
···11+//go:build cgo
22+33+/*
44+Copyright 2025.
55+66+Licensed under the Apache License, Version 2.0 (the "License");
77+you may not use this file except in compliance with the License.
88+You may obtain a copy of the License at
99+1010+ http://www.apache.org/licenses/LICENSE-2.0
1111+1212+Unless required by applicable law or agreed to in writing, software
1313+distributed under the License is distributed on an "AS IS" BASIS,
1414+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+See the License for the specific language governing permissions and
1616+limitations under the License.
1717+*/
1818+1919+package discovery
2020+2121+import (
2222+ "time"
2323+2424+ "github.com/jochenvg/go-udev"
2525+)
2626+2727+type Udev = udev.Udev
2828+type Monitor = udev.Monitor
2929+type UDevDevice = udev.Device
3030+type Enumerate = udev.Enumerate
3131+3232+// convertUdevDevice converts a go-udev Device to our USBDevice format
3333+func (u *USBDiscoverer) convertUdevDevice(device *UDevDevice) *USBDevice {
3434+ // Only process actual USB devices, not interfaces
3535+ if device.PropertyValue("DEVTYPE") != "usb_device" {
3636+ return nil
3737+ }
3838+3939+ vendorID := device.PropertyValue("ID_VENDOR_ID")
4040+ productID := device.PropertyValue("ID_MODEL_ID")
4141+4242+ // Skip devices without vendor/product IDs
4343+ if vendorID == "" || productID == "" {
4444+ return nil
4545+ }
4646+4747+ return &USBDevice{
4848+ VendorID: vendorID,
4949+ ProductID: productID,
5050+ SerialNumber: device.PropertyValue("ID_SERIAL_SHORT"),
5151+ DevicePath: device.Devnode(),
5252+ Manufacturer: device.PropertyValue("ID_VENDOR"),
5353+ Product: device.PropertyValue("ID_MODEL"),
5454+ DeviceInfo: device.Properties(),
5555+ }
5656+}
5757+5858+// handleDeviceEvent processes a single USB device event
5959+func (u *USBDiscoverer) handleDeviceEvent(device *UDevDevice) {
6060+ action := device.Action()
6161+6262+ // Only process add/remove events
6363+ if action != "add" && action != "remove" {
6464+ return
6565+ }
6666+6767+ // Convert to USBDevice
6868+ usbDev := u.convertUdevDevice(device)
6969+ if usbDev == nil {
7070+ return
7171+ }
7272+7373+ u.logger.V(2).Info("Received USB event",
7474+ "action", action,
7575+ "vendor", usbDev.VendorID,
7676+ "product", usbDev.ProductID,
7777+ "serial", usbDev.SerialNumber)
7878+7979+ // Check which active specs match this device
8080+ for hsmDeviceName, spec := range u.activeSpecs {
8181+ if u.matchesSpec(*usbDev, spec) {
8282+ event := USBEvent{
8383+ Action: action,
8484+ Device: *usbDev,
8585+ Timestamp: time.Now(),
8686+ HSMDeviceName: hsmDeviceName,
8787+ }
8888+8989+ select {
9090+ case u.eventChannel <- event:
9191+ u.logger.V(1).Info("Sent USB event",
9292+ "action", action,
9393+ "device", hsmDeviceName,
9494+ "vendor", usbDev.VendorID,
9595+ "product", usbDev.ProductID,
9696+ "serial", usbDev.SerialNumber)
9797+ default:
9898+ u.logger.Error(nil, "USB event channel full, dropping event", "action", action)
9999+ }
100100+ }
101101+ }
102102+}
+47-70
internal/discovery/usb_nocgo.go
···11//go:build !cgo
22-// +build !cgo
3243/*
54Copyright 2025.
···2221import (
2322 "context"
2423 "fmt"
2525- "time"
2424+)
26252727- "github.com/go-logr/logr"
2828- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929- ctrl "sigs.k8s.io/controller-runtime"
2626+// UDevDevice is a stub type for non-CGO builds
2727+type UDevDevice struct{}
2828+2929+// Monitor is a stub type for non-CGO builds
3030+type Monitor struct{}
30313131- hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1"
3232-)
3232+// Udev is a stub type for non-CGO builds
3333+type Udev struct{}
33343434-// USBDevice represents a discovered USB device
3535-type USBDevice struct {
3636- VendorID string
3737- ProductID string
3838- SerialNumber string
3939- DevicePath string
4040- Manufacturer string
4141- Product string
4242- DeviceInfo map[string]string
3535+// NewEnumerate creates a stub enumerate object for non-CGO builds
3636+func (u *Udev) NewEnumerate() *Enumerate {
3737+ return &Enumerate{}
4338}
44394545-// USBEvent represents a USB device event
4646-type USBEvent struct {
4747- Action string // "add" or "remove"
4848- Device USBDevice // The device that changed
4949- Timestamp time.Time
5050- HSMDeviceName string // Which HSMDevice spec this event relates to
4040+// NewMonitorFromNetlink creates a stub monitor for non-CGO builds
4141+func (u *Udev) NewMonitorFromNetlink(string) *Monitor {
4242+ return &Monitor{}
5143}
52445353-// USBDiscoverer handles USB device discovery and monitoring (no-CGO stub)
5454-type USBDiscoverer struct {
5555- logger logr.Logger
5656- eventChannel chan USBEvent
5757- activeSpecs map[string]*hsmv1alpha1.USBDeviceSpec
4545+// Enumerate is a stub type for non-CGO builds
4646+type Enumerate struct{}
4747+4848+// AddMatchSubsystem does nothing in non-CGO builds
4949+func (e *Enumerate) AddMatchSubsystem(string) error {
5050+ return nil
5851}
59526060-// NewUSBDiscoverer creates a new USB device discoverer (no-CGO stub)
6161-func NewUSBDiscoverer() *USBDiscoverer {
6262- logger := ctrl.Log.WithName("usb-discoverer-nocgo")
5353+// AddMatchProperty does nothing in non-CGO builds
5454+func (e *Enumerate) AddMatchProperty(string, string) error {
5555+ return nil
5656+}
63576464- return &USBDiscoverer{
6565- logger: logger,
6666- eventChannel: make(chan USBEvent, 100),
6767- activeSpecs: make(map[string]*hsmv1alpha1.USBDeviceSpec),
6868- }
5858+// Devices returns an empty slice for non-CGO builds
5959+func (e *Enumerate) Devices() ([]*UDevDevice, error) {
6060+ return []*UDevDevice{}, nil
6961}
70627171-// NewUSBDiscovererWithMethod creates a new USB device discoverer (method parameter is ignored, kept for compatibility)
7272-func NewUSBDiscovererWithMethod(method string) *USBDiscoverer {
7373- return NewUSBDiscoverer()
7474-}
6363+// DeviceChan returns empty channels for non-CGO builds
6464+func (m *Monitor) DeviceChan(ctx context.Context) (<-chan *UDevDevice, <-chan error, error) {
6565+ deviceChan := make(chan *UDevDevice)
6666+ errorChan := make(chan error)
75677676-// DiscoverDevices finds USB devices matching the given specification (no-CGO stub - returns empty)
7777-func (u *USBDiscoverer) DiscoverDevices(ctx context.Context, spec *hsmv1alpha1.USBDeviceSpec) ([]USBDevice, error) {
7878- u.logger.Info("USB device discovery not available (CGO disabled)",
7979- "vendorId", spec.VendorID,
8080- "productId", spec.ProductID)
6868+ // Close channels immediately since no devices will be found
6969+ close(deviceChan)
7070+ close(errorChan)
81718282- // Return empty slice - no devices found without udev
8383- return []USBDevice{}, nil
7272+ return deviceChan, errorChan, nil
8473}
85748686-// StartEventMonitoring starts monitoring for USB device events (no-CGO stub - does nothing)
8787-func (u *USBDiscoverer) StartEventMonitoring(ctx context.Context) error {
8888- u.logger.Info("USB event monitoring not available (CGO disabled)")
7575+// FilterAddMatchSubsystem does nothing in non-CGO builds
7676+func (m *Monitor) FilterAddMatchSubsystem(string) error {
8977 return nil
9078}
91799292-// AddSpecForMonitoring adds a device spec to monitor for events (no-CGO stub)
9393-func (u *USBDiscoverer) AddSpecForMonitoring(hsmDeviceName string, spec *hsmv1alpha1.USBDeviceSpec) {
9494- u.logger.V(1).Info("USB monitoring not available (CGO disabled)", "device", hsmDeviceName)
9595- u.activeSpecs[hsmDeviceName] = spec
8080+// convertUdevDevice always returns nil for non-CGO builds
8181+func (u *USBDiscoverer) convertUdevDevice(device *UDevDevice) *USBDevice {
8282+ return nil
9683}
97849898-// RemoveSpecFromMonitoring removes a device spec from event monitoring (no-CGO stub)
9999-func (u *USBDiscoverer) RemoveSpecFromMonitoring(hsmDeviceName string) {
100100- u.logger.V(1).Info("USB monitoring not available (CGO disabled)", "device", hsmDeviceName)
101101- delete(u.activeSpecs, hsmDeviceName)
8585+// handleDeviceEvent does nothing for non-CGO builds
8686+func (u *USBDiscoverer) handleDeviceEvent(device *UDevDevice) {
8787+ // No-op for non-CGO builds
10288}
10389104104-// GetEventChannel returns the channel for USB events (no-CGO stub)
105105-func (u *USBDiscoverer) GetEventChannel() <-chan USBEvent {
106106- return u.eventChannel
107107-}
108108-109109-// StopEventMonitoring stops USB device event monitoring (no-CGO stub)
110110-func (u *USBDiscoverer) StopEventMonitoring() {
111111- u.logger.Info("USB event monitoring not available (CGO disabled)")
9090+// init logs a warning about using fallback mode
9191+func init() {
9292+ // Note: We can't use the logger here since it's not available during init
9393+ fmt.Println("WARNING: USB discovery running in fallback mode (CGO disabled). No USB devices will be detected.")
11294}
113113-114114-// IsEventMonitoringActive returns whether event monitoring is active (no-CGO stub - always false)
115115-func (u *USBDiscoverer) IsEventMonitoringActive() bool {
116116- return false
117117-}
+392
internal/hsm/pkcs11_cgo.go
···11+//go:build cgo
22+33+/*
44+Copyright 2025.
55+66+Licensed under the Apache License, Version 2.0 (the "License");
77+you may not use this file except in compliance with the License.
88+You may obtain a copy of the License at
99+1010+ http://www.apache.org/licenses/LICENSE-2.0
1111+1212+Unless required by applicable law or agreed to in writing, software
1313+distributed under the License is distributed on an "AS IS" BASIS,
1414+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+See the License for the specific language governing permissions and
1616+limitations under the License.
1717+*/
1818+1919+package hsm
2020+2121+import (
2222+ "fmt"
2323+ "strings"
2424+2525+ "github.com/miekg/pkcs11"
2626+)
2727+2828+// CGO-specific types
2929+type Session struct {
3030+ ctx *pkcs11.Ctx
3131+ session pkcs11.SessionHandle
3232+ slot uint
3333+}
3434+3535+type ObjectHandle = pkcs11.ObjectHandle
3636+3737+// initializePKCS11 establishes connection to the HSM via PKCS#11
3838+func initializePKCS11(config Config, pin string) (*Session, uint, error) {
3939+ // Initialize PKCS#11 context
4040+ ctx := pkcs11.New(config.PKCS11LibraryPath)
4141+ if ctx == nil {
4242+ return nil, 0, fmt.Errorf("failed to create PKCS#11 context for library: %s", config.PKCS11LibraryPath)
4343+ }
4444+4545+ // Initialize the library
4646+ if err := ctx.Initialize(); err != nil {
4747+ return nil, 0, fmt.Errorf("failed to initialize PKCS#11 library: %w", err)
4848+ }
4949+5050+ // Find the slot
5151+ slots, err := ctx.GetSlotList(true) // true = only slots with tokens
5252+ if err != nil {
5353+ if finErr := ctx.Finalize(); finErr != nil {
5454+ // Ignore finalize error, return original error
5555+ _ = finErr
5656+ }
5757+ ctx.Destroy()
5858+ return nil, 0, fmt.Errorf("failed to get slot list: %w", err)
5959+ }
6060+6161+ if len(slots) == 0 {
6262+ if finErr := ctx.Finalize(); finErr != nil {
6363+ // Ignore finalize error, return original error
6464+ _ = finErr
6565+ }
6666+ ctx.Destroy()
6767+ return nil, 0, fmt.Errorf("no slots with tokens found")
6868+ }
6969+7070+ // Use specified slot ID or find by token label
7171+ var targetSlot uint
7272+ found := false
7373+7474+ if config.UseSlotID {
7575+ // Use specified slot ID
7676+ for _, slot := range slots {
7777+ if slot == config.SlotID {
7878+ targetSlot = slot
7979+ found = true
8080+ break
8181+ }
8282+ }
8383+ if !found {
8484+ if finErr := ctx.Finalize(); finErr != nil {
8585+ // Ignore finalize error, return original error
8686+ _ = finErr
8787+ }
8888+ ctx.Destroy()
8989+ return nil, 0, fmt.Errorf("specified slot ID %d not found", config.SlotID)
9090+ }
9191+ } else if config.TokenLabel != "" {
9292+ // Find slot by token label
9393+ for _, slot := range slots {
9494+ tokenInfo, err := ctx.GetTokenInfo(slot)
9595+ if err != nil {
9696+ continue
9797+ }
9898+ if strings.TrimSpace(tokenInfo.Label) == config.TokenLabel {
9999+ targetSlot = slot
100100+ found = true
101101+ break
102102+ }
103103+ }
104104+ if !found {
105105+ if finErr := ctx.Finalize(); finErr != nil {
106106+ // Ignore finalize error, return original error
107107+ _ = finErr
108108+ }
109109+ ctx.Destroy()
110110+ return nil, 0, fmt.Errorf("token with label '%s' not found", config.TokenLabel)
111111+ }
112112+ } else {
113113+ // Use first available slot
114114+ targetSlot = slots[0]
115115+ }
116116+117117+ // Open session
118118+ session, err := ctx.OpenSession(targetSlot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
119119+ if err != nil {
120120+ if finErr := ctx.Finalize(); finErr != nil {
121121+ // Ignore finalize error, return original error
122122+ _ = finErr
123123+ }
124124+ ctx.Destroy()
125125+ return nil, 0, fmt.Errorf("failed to open session: %w", err)
126126+ }
127127+128128+ // Login with PIN
129129+ if err := ctx.Login(session, pkcs11.CKU_USER, pin); err != nil {
130130+ if closeErr := ctx.CloseSession(session); closeErr != nil {
131131+ // Ignore close error, return original error
132132+ _ = closeErr
133133+ }
134134+ if finErr := ctx.Finalize(); finErr != nil {
135135+ // Ignore finalize error, return original error
136136+ _ = finErr
137137+ }
138138+ ctx.Destroy()
139139+ return nil, 0, fmt.Errorf("failed to login with PIN: %w", err)
140140+ }
141141+142142+ return &Session{
143143+ ctx: ctx,
144144+ session: session,
145145+ slot: targetSlot,
146146+ }, targetSlot, nil
147147+}
148148+149149+// closePKCS11 terminates the HSM connection
150150+func closePKCS11(session *Session) error {
151151+ if session == nil {
152152+ return nil
153153+ }
154154+155155+ // Logout and close session
156156+ if session.ctx != nil && session.session != 0 {
157157+ if logoutErr := session.ctx.Logout(session.session); logoutErr != nil {
158158+ // Ignore logout error but continue
159159+ _ = logoutErr
160160+ }
161161+ if closeErr := session.ctx.CloseSession(session.session); closeErr != nil {
162162+ // Ignore close error but continue
163163+ _ = closeErr
164164+ }
165165+ }
166166+167167+ // Finalize and destroy context
168168+ if session.ctx != nil {
169169+ if finErr := session.ctx.Finalize(); finErr != nil {
170170+ // Ignore finalize error but continue
171171+ _ = finErr
172172+ }
173173+ session.ctx.Destroy()
174174+ }
175175+176176+ return nil
177177+}
178178+179179+// getTokenInfoPKCS11 returns information about the HSM token
180180+func getTokenInfoPKCS11(session *Session, slot uint) (*tokenInfo, error) {
181181+ if session == nil {
182182+ return nil, fmt.Errorf("session is nil")
183183+ }
184184+185185+ // Get token information from PKCS#11
186186+ pkcs11TokenInfo, err := session.ctx.GetTokenInfo(session.slot)
187187+ if err != nil {
188188+ return nil, fmt.Errorf("failed to get token info: %w", err)
189189+ }
190190+191191+ // Get slot information
192192+ slotInfo, slotErr := session.ctx.GetSlotInfo(session.slot)
193193+194194+ info := &tokenInfo{
195195+ Label: strings.TrimSpace(pkcs11TokenInfo.Label),
196196+ ManufacturerID: strings.TrimSpace(pkcs11TokenInfo.ManufacturerID),
197197+ Model: strings.TrimSpace(pkcs11TokenInfo.Model),
198198+ SerialNumber: strings.TrimSpace(pkcs11TokenInfo.SerialNumber),
199199+ FirmwareVersion: fmt.Sprintf("%d.%d", pkcs11TokenInfo.FirmwareVersion.Major, pkcs11TokenInfo.FirmwareVersion.Minor),
200200+ }
201201+202202+ // Add slot info if available
203203+ if slotErr == nil {
204204+ if info.ManufacturerID == "" {
205205+ info.ManufacturerID = strings.TrimSpace(slotInfo.ManufacturerID)
206206+ }
207207+ if info.Model == "" {
208208+ info.Model = strings.TrimSpace(slotInfo.SlotDescription)
209209+ }
210210+ }
211211+212212+ return info, nil
213213+}
214214+215215+// findObjectsPKCS11 finds PKCS#11 data objects matching the given path
216216+func findObjectsPKCS11(session *Session, path string) ([]pkcs11Object, error) {
217217+ if session == nil {
218218+ return nil, fmt.Errorf("session is nil")
219219+ }
220220+221221+ // Find all data objects (we'll filter by label after)
222222+ template := []*pkcs11.Attribute{
223223+ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
224224+ }
225225+226226+ if err := session.ctx.FindObjectsInit(session.session, template); err != nil {
227227+ return nil, fmt.Errorf("failed to initialize object search: %w", err)
228228+ }
229229+ defer func() {
230230+ if finalErr := session.ctx.FindObjectsFinal(session.session); finalErr != nil {
231231+ // Ignore finalize error but continue
232232+ _ = finalErr
233233+ }
234234+ }()
235235+236236+ // Get all matching objects
237237+ objs, _, err := session.ctx.FindObjects(session.session, 1000) // Max 1000 objects
238238+ if err != nil {
239239+ return nil, fmt.Errorf("failed to find objects: %w", err)
240240+ }
241241+242242+ // Pre-allocate slice for better performance
243243+ objects := make([]pkcs11Object, 0, len(objs))
244244+245245+ // Read each data object
246246+ for _, obj := range objs {
247247+ // Get the label and value
248248+ attrs, err := session.ctx.GetAttributeValue(session.session, obj, []*pkcs11.Attribute{
249249+ pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
250250+ pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
251251+ })
252252+ if err != nil {
253253+ continue // Skip objects we can't read
254254+ }
255255+256256+ if len(attrs) < 2 || len(attrs[0].Value) == 0 {
257257+ continue // Skip objects without proper attributes
258258+ }
259259+260260+ label := string(attrs[0].Value)
261261+ value := attrs[1].Value
262262+263263+ // If path is specified, filter by it
264264+ if path != "" && !strings.HasPrefix(label, path) {
265265+ continue
266266+ }
267267+268268+ objects = append(objects, pkcs11Object{
269269+ Label: label,
270270+ Value: value,
271271+ Handle: obj,
272272+ })
273273+ }
274274+275275+ return objects, nil
276276+}
277277+278278+// createObjectPKCS11 creates a new PKCS#11 data object
279279+func createObjectPKCS11(session *Session, label string, value []byte) (ObjectHandle, error) {
280280+ if session == nil {
281281+ return 0, fmt.Errorf("session is nil")
282282+ }
283283+284284+ // Infer data type from content
285285+ dataType := InferDataType(value)
286286+287287+ // Get OID for data type
288288+ oid, err := GetOIDForDataType(dataType)
289289+ if err != nil {
290290+ oid = OIDPlaintext // Default fallback
291291+ }
292292+293293+ // Encode OID as DER
294294+ derOID, err := EncodeDER(oid)
295295+ if err != nil {
296296+ derOID = nil // Will skip CKA_OBJECT_ID if encoding fails
297297+ }
298298+299299+ // Build template with proper attributes
300300+ template := []*pkcs11.Attribute{
301301+ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
302302+ pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
303303+ pkcs11.NewAttribute(pkcs11.CKA_APPLICATION, applicationName), // Proper application name
304304+ pkcs11.NewAttribute(pkcs11.CKA_VALUE, value),
305305+ pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), // Store persistently
306306+ pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), // Require authentication
307307+ pkcs11.NewAttribute(pkcs11.CKA_MODIFIABLE, true), // Allow updates
308308+ }
309309+310310+ // Add OID if we successfully encoded it
311311+ if derOID != nil {
312312+ template = append(template, pkcs11.NewAttribute(pkcs11.CKA_OBJECT_ID, derOID))
313313+ }
314314+315315+ obj, err := session.ctx.CreateObject(session.session, template)
316316+ if err != nil {
317317+ return 0, fmt.Errorf("failed to create data object: %w", err)
318318+ }
319319+320320+ return obj, nil
321321+}
322322+323323+// deleteSecretObjectsPKCS11 removes all data objects matching the given path prefix
324324+func deleteSecretObjectsPKCS11(session *Session, path string) error {
325325+ if session == nil {
326326+ return fmt.Errorf("session is nil")
327327+ }
328328+329329+ // Find all data objects (we'll filter by label after)
330330+ template := []*pkcs11.Attribute{
331331+ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
332332+ }
333333+334334+ if err := session.ctx.FindObjectsInit(session.session, template); err != nil {
335335+ return fmt.Errorf("failed to initialize object search: %w", err)
336336+ }
337337+ defer func() {
338338+ if finalErr := session.ctx.FindObjectsFinal(session.session); finalErr != nil {
339339+ // Ignore finalize error but continue
340340+ _ = finalErr
341341+ }
342342+ }()
343343+344344+ // Get all matching objects
345345+ objs, _, err := session.ctx.FindObjects(session.session, 100) // Max 100 objects
346346+ if err != nil {
347347+ return fmt.Errorf("failed to find objects: %w", err)
348348+ }
349349+350350+ // Delete each object that matches our path
351351+ for _, obj := range objs {
352352+ // Get the label to check if this object matches our path
353353+ labelAttr, err := session.ctx.GetAttributeValue(session.session, obj, []*pkcs11.Attribute{
354354+ pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
355355+ })
356356+ if err != nil {
357357+ continue
358358+ }
359359+360360+ if len(labelAttr) == 0 || len(labelAttr[0].Value) == 0 {
361361+ continue
362362+ }
363363+364364+ label := string(labelAttr[0].Value)
365365+ // Only delete objects that match our path
366366+ if !strings.HasPrefix(label, path) {
367367+ continue
368368+ }
369369+370370+ if err := session.ctx.DestroyObject(session.session, obj); err != nil {
371371+ // Log error but continue with other objects
372372+ continue
373373+ }
374374+ }
375375+376376+ return nil
377377+}
378378+379379+// changePINPKCS11 changes the HSM PIN
380380+func changePINPKCS11(session *Session, oldPIN, newPIN string) error {
381381+ if session == nil {
382382+ return fmt.Errorf("session is nil")
383383+ }
384384+385385+ // Use PKCS#11 SetPIN function to change the PIN
386386+ // Note: This changes the user PIN (not SO PIN)
387387+ if err := session.ctx.SetPIN(session.session, oldPIN, newPIN); err != nil {
388388+ return fmt.Errorf("failed to change HSM PIN: %w", err)
389389+ }
390390+391391+ return nil
392392+}
+78-407
internal/hsm/pkcs11_client.go
···11-//go:build cgo
22-// +build cgo
33-41/*
52Copyright 2025.
63···2825 "time"
29263027 "github.com/go-logr/logr"
3131- "github.com/miekg/pkcs11"
3228 ctrl "sigs.k8s.io/controller-runtime"
3329)
3430···4440 logger logr.Logger
4541 mutex sync.RWMutex
46424747- // PKCS#11 objects
4848- ctx *pkcs11.Ctx
4949- session pkcs11.SessionHandle
4343+ // Internal state
4444+ session *Session // Will be concrete type in CGO, stub in non-CGO
5045 slot uint
5146 connected bool
52475353- // Data object cache for faster lookups
5454- dataObjects map[string]pkcs11.ObjectHandle
4848+ // Data object cache for faster lookups (CGO only)
4949+ dataObjects map[string]ObjectHandle
5050+}
5151+5252+// pkcs11Object represents a PKCS#11 data object
5353+type pkcs11Object struct {
5454+ Label string
5555+ Value []byte
5656+ Handle ObjectHandle // Will be concrete type in CGO, stub in non-CGO
5757+}
5858+5959+// tokenInfo represents HSM token information
6060+type tokenInfo struct {
6161+ Label string
6262+ ManufacturerID string
6363+ Model string
6464+ SerialNumber string
6565+ FirmwareVersion string
5566}
56675768// NewPKCS11Client creates a new PKCS#11 HSM client
5869func NewPKCS11Client() *PKCS11Client {
5970 return &PKCS11Client{
6071 logger: ctrl.Log.WithName("hsm-pkcs11-client"),
6161- dataObjects: make(map[string]pkcs11.ObjectHandle),
7272+ dataObjects: make(map[string]ObjectHandle),
6273 }
6374}
6475···7384 "slot", config.SlotID,
7485 "tokenLabel", config.TokenLabel)
75867676- // Validate configuration
8787+ // Common validation
7788 if config.PKCS11LibraryPath == "" {
7889 return fmt.Errorf("PKCS11LibraryPath is required")
7990 }
···8192 if config.PINProvider == nil {
8293 return fmt.Errorf("PINProvider is required for HSM authentication")
8394 }
8484-8585- // Initialize PKCS#11 context
8686- c.ctx = pkcs11.New(config.PKCS11LibraryPath)
8787- if c.ctx == nil {
8888- return fmt.Errorf("failed to create PKCS#11 context for library: %s", config.PKCS11LibraryPath)
8989- }
9090-9191- // Initialize the library
9292- if err := c.ctx.Initialize(); err != nil {
9393- return fmt.Errorf("failed to initialize PKCS#11 library: %w", err)
9494- }
9595-9696- // Find the slot
9797- slots, err := c.ctx.GetSlotList(true) // true = only slots with tokens
9898- if err != nil {
9999- if finErr := c.ctx.Finalize(); finErr != nil {
100100- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
101101- }
102102- c.ctx.Destroy()
103103- return fmt.Errorf("failed to get slot list: %w", err)
104104- }
105105-106106- if len(slots) == 0 {
107107- if finErr := c.ctx.Finalize(); finErr != nil {
108108- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
109109- }
110110- c.ctx.Destroy()
111111- return fmt.Errorf("no slots with tokens found")
112112- }
113113-114114- // Use specified slot ID or find by token label
115115- var targetSlot uint
116116- found := false
117117-118118- if config.UseSlotID {
119119- // Use specified slot ID
120120- for _, slot := range slots {
121121- if slot == config.SlotID {
122122- targetSlot = slot
123123- found = true
124124- break
125125- }
126126- }
127127- if !found {
128128- if finErr := c.ctx.Finalize(); finErr != nil {
129129- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
130130- }
131131- c.ctx.Destroy()
132132- return fmt.Errorf("specified slot ID %d not found", config.SlotID)
133133- }
134134- } else if config.TokenLabel != "" {
135135- // Find slot by token label
136136- for _, slot := range slots {
137137- tokenInfo, err := c.ctx.GetTokenInfo(slot)
138138- if err != nil {
139139- c.logger.V(1).Info("Failed to get token info for slot", "slot", slot, "error", err)
140140- continue
141141- }
142142- if strings.TrimSpace(tokenInfo.Label) == config.TokenLabel {
143143- targetSlot = slot
144144- found = true
145145- break
146146- }
147147- }
148148- if !found {
149149- if finErr := c.ctx.Finalize(); finErr != nil {
150150- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
151151- }
152152- c.ctx.Destroy()
153153- return fmt.Errorf("token with label '%s' not found", config.TokenLabel)
154154- }
155155- } else {
156156- // Use first available slot
157157- targetSlot = slots[0]
158158- }
159159-160160- c.slot = targetSlot
161161- c.logger.Info("Using HSM slot", "slot", targetSlot)
162162-163163- // Open session
164164- session, err := c.ctx.OpenSession(targetSlot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
165165- if err != nil {
166166- if finErr := c.ctx.Finalize(); finErr != nil {
167167- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
168168- }
169169- c.ctx.Destroy()
170170- return fmt.Errorf("failed to open session: %w", err)
171171- }
172172- c.session = session
1739517496 // Get PIN from provider
17597 pin, err := config.PINProvider.GetPIN(ctx)
17698 if err != nil {
177177- if closeErr := c.ctx.CloseSession(session); closeErr != nil {
178178- c.logger.V(1).Info("Failed to close session", "error", closeErr)
179179- }
180180- if finErr := c.ctx.Finalize(); finErr != nil {
181181- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
182182- }
183183- c.ctx.Destroy()
18499 return fmt.Errorf("failed to get PIN from provider: %w", err)
185100 }
186101187187- // Login with PIN
188188- if err := c.ctx.Login(session, pkcs11.CKU_USER, pin); err != nil {
189189- if closeErr := c.ctx.CloseSession(session); closeErr != nil {
190190- c.logger.V(1).Info("Failed to close session", "error", closeErr)
191191- }
192192- if finErr := c.ctx.Finalize(); finErr != nil {
193193- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
194194- }
195195- c.ctx.Destroy()
196196- return fmt.Errorf("failed to login with PIN: %w", err)
102102+ // Call platform-specific initialization
103103+ session, slot, err := initializePKCS11(config, pin)
104104+ if err != nil {
105105+ return err
197106 }
198107108108+ c.session = session
109109+ c.slot = slot
199110 c.connected = true
200200- c.logger.Info("HSM connection established successfully", "slot", targetSlot)
111111+ c.logger.Info("HSM connection established successfully", "slot", slot)
201112 return nil
202113}
203114···212123213124 c.logger.Info("Closing HSM connection")
214125215215- // Logout and close session
216216- if c.ctx != nil && c.session != 0 {
217217- if logoutErr := c.ctx.Logout(c.session); logoutErr != nil {
218218- c.logger.V(1).Info("Failed to logout from HSM session", "error", logoutErr)
219219- }
220220- if closeErr := c.ctx.CloseSession(c.session); closeErr != nil {
221221- c.logger.V(1).Info("Failed to close HSM session", "error", closeErr)
222222- }
223223- }
224224-225225- // Finalize and destroy context
226226- if c.ctx != nil {
227227- if finErr := c.ctx.Finalize(); finErr != nil {
228228- c.logger.V(1).Info("Failed to finalize PKCS#11 context", "error", finErr)
229229- }
230230- c.ctx.Destroy()
126126+ // Call platform-specific cleanup
127127+ if err := closePKCS11(c.session); err != nil {
128128+ c.logger.V(1).Info("Error during PKCS#11 cleanup", "error", err)
231129 }
232130233131 c.connected = false
234234- c.session = 0
235235- c.ctx = nil
236236- c.dataObjects = make(map[string]pkcs11.ObjectHandle)
132132+ c.session = nil
133133+ c.dataObjects = make(map[string]ObjectHandle)
237134238135 return nil
239136}
···247144 return nil, fmt.Errorf("HSM not connected")
248145 }
249146250250- // Get token information from PKCS#11
251251- tokenInfo, err := c.ctx.GetTokenInfo(c.slot)
147147+ // Get token information via helper
148148+ tokenInfo, err := getTokenInfoPKCS11(c.session, c.slot)
252149 if err != nil {
253150 return nil, fmt.Errorf("failed to get token info: %w", err)
254151 }
255152256256- // Get slot information
257257- slotInfo, slotErr := c.ctx.GetSlotInfo(c.slot)
258258- if slotErr != nil {
259259- c.logger.V(1).Info("Failed to get slot info", "error", slotErr)
260260- }
261261-262153 info := &HSMInfo{
263154 Label: strings.TrimSpace(tokenInfo.Label),
264155 Manufacturer: strings.TrimSpace(tokenInfo.ManufacturerID),
265156 Model: strings.TrimSpace(tokenInfo.Model),
266157 SerialNumber: strings.TrimSpace(tokenInfo.SerialNumber),
267267- FirmwareVersion: fmt.Sprintf("%d.%d", tokenInfo.FirmwareVersion.Major, tokenInfo.FirmwareVersion.Minor),
268268- }
269269-270270- // Add slot info if available
271271- if slotErr == nil {
272272- if info.Manufacturer == "" {
273273- info.Manufacturer = strings.TrimSpace(slotInfo.ManufacturerID)
274274- }
275275- if info.Model == "" {
276276- info.Model = strings.TrimSpace(slotInfo.SlotDescription)
277277- }
158158+ FirmwareVersion: tokenInfo.FirmwareVersion,
278159 }
279160280161 return info, nil
···291172292173 c.logger.V(1).Info("Reading secret from HSM", "path", path)
293174294294- // Find all data objects (we'll filter by label after)
295295- template := []*pkcs11.Attribute{
296296- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
297297- }
298298-299299- if err := c.ctx.FindObjectsInit(c.session, template); err != nil {
300300- return nil, fmt.Errorf("failed to initialize object search: %w", err)
301301- }
302302- defer func() {
303303- if finalErr := c.ctx.FindObjectsFinal(c.session); finalErr != nil {
304304- c.logger.V(1).Info("Failed to finalize object search", "error", finalErr)
305305- }
306306- }()
307307-308308- // Get all matching objects
309309- objs, _, err := c.ctx.FindObjects(c.session, 100) // Max 100 objects
175175+ // Find objects matching the path
176176+ objects, err := findObjectsPKCS11(c.session, path)
310177 if err != nil {
311178 return nil, fmt.Errorf("failed to find objects: %w", err)
312179 }
313180314181 data := make(SecretData)
315315- // Track if we found any objects for this path
316182 matchingObjects := 0
317183318318- // Read each data object and filter by path
319319- for _, obj := range objs {
320320- // Get the label to determine if this object matches our path
321321- labelAttr, err := c.ctx.GetAttributeValue(c.session, obj, []*pkcs11.Attribute{
322322- pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
323323- })
324324- if err != nil {
325325- c.logger.V(1).Info("Failed to get object label", "error", err)
326326- continue
327327- }
328328-329329- if len(labelAttr) == 0 || len(labelAttr[0].Value) == 0 {
330330- c.logger.V(1).Info("Object has no label, skipping")
331331- continue
332332- }
333333-334334- label := string(labelAttr[0].Value)
335335-184184+ // Process each object
185185+ for _, obj := range objects {
336186 // Check if this object matches our path
337337- if !strings.HasPrefix(label, path) {
338338- continue // Skip objects that don't match our path
187187+ if !strings.HasPrefix(obj.Label, path) {
188188+ continue
339189 }
340190341191 // Skip metadata objects when reading secrets
342342- if strings.HasSuffix(label, metadataKeySuffix) {
192192+ if strings.HasSuffix(obj.Label, metadataKeySuffix) {
343193 continue
344194 }
345195346196 matchingObjects++
347197348198 // Extract key name from label (remove path prefix)
349349- key := strings.TrimPrefix(label, path)
199199+ key := strings.TrimPrefix(obj.Label, path)
350200 key = strings.TrimPrefix(key, "/")
351201 if key == "" {
352202 key = defaultKeyName // Default key name
353203 }
354204355355- // Get the actual data value
356356- valueAttr, err := c.ctx.GetAttributeValue(c.session, obj, []*pkcs11.Attribute{
357357- pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
358358- })
359359- if err != nil {
360360- c.logger.V(1).Info("Failed to get object value", "key", key, "error", err)
361361- continue
362362- }
363363-364364- if len(valueAttr) > 0 && len(valueAttr[0].Value) > 0 {
365365- data[key] = valueAttr[0].Value
366366- }
205205+ data[key] = obj.Value
367206 }
368207369208 if matchingObjects == 0 {
···393232 "path", path, "keys", len(data))
394233395234 // First, delete any existing objects for this path to avoid duplicates
396396- if err := c.deleteSecretObjects(path); err != nil {
235235+ if err := deleteSecretObjectsPKCS11(c.session, path); err != nil {
397236 c.logger.V(1).Info("Failed to delete existing objects (may not exist)", "error", err)
398237 }
399238···404243 label = path + "/" + key
405244 }
406245407407- // Infer data type from content
408408- dataType := InferDataType(value)
409409-410410- // Get OID for data type
411411- oid, err := GetOIDForDataType(dataType)
412412- if err != nil {
413413- c.logger.V(1).Info("Failed to get OID for data type, using default",
414414- "dataType", dataType, "error", err)
415415- oid = OIDPlaintext // Default fallback
416416- }
417417-418418- // Encode OID as DER
419419- derOID, err := EncodeDER(oid)
420420- if err != nil {
421421- c.logger.V(1).Info("Failed to encode OID as DER", "error", err)
422422- derOID = nil // Will skip CKA_OBJECT_ID if encoding fails
423423- }
424424-425425- // Build template with proper attributes
426426- template := []*pkcs11.Attribute{
427427- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
428428- pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
429429- pkcs11.NewAttribute(pkcs11.CKA_APPLICATION, applicationName), // Proper application name
430430- pkcs11.NewAttribute(pkcs11.CKA_VALUE, value),
431431- pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), // Store persistently
432432- pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), // Require authentication
433433- pkcs11.NewAttribute(pkcs11.CKA_MODIFIABLE, true), // Allow updates
434434- }
435435-436436- // Add OID if we successfully encoded it
437437- if derOID != nil {
438438- template = append(template, pkcs11.NewAttribute(pkcs11.CKA_OBJECT_ID, derOID))
439439- }
440440-441441- obj, err := c.ctx.CreateObject(c.session, template)
246246+ // Create the object via helper
247247+ handle, err := createObjectPKCS11(c.session, label, value)
442248 if err != nil {
443249 return fmt.Errorf("failed to create data object for key '%s': %w", key, err)
444250 }
445251446252 // Cache the object handle for faster future lookups
447447- c.dataObjects[label] = obj
253253+ c.dataObjects[label] = handle
448254449449- c.logger.V(2).Info("Created data object", "path", path, "key", key, "label", label, "dataType", dataType)
255255+ c.logger.V(2).Info("Created data object", "path", path, "key", key, "label", label)
450256 }
451257452258 c.logger.Info("Successfully wrote secret to HSM", "path", path)
···469275 // Create metadata object label
470276 metadataLabel := path + metadataKeySuffix
471277472472- // Get OID for JSON data type
473473- oid, err := GetOIDForDataType(DataTypeJson)
474474- if err != nil {
475475- c.logger.V(1).Info("Failed to get OID for JSON metadata", "error", err)
476476- oid = OIDJson // Fallback
477477- }
478478-479479- // Encode OID as DER
480480- derOID, err := EncodeDER(oid)
481481- if err != nil {
482482- c.logger.V(1).Info("Failed to encode metadata OID as DER", "error", err)
483483- derOID = nil
484484- }
485485-486486- // Build metadata object template
487487- template := []*pkcs11.Attribute{
488488- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
489489- pkcs11.NewAttribute(pkcs11.CKA_LABEL, metadataLabel),
490490- pkcs11.NewAttribute(pkcs11.CKA_APPLICATION, applicationName),
491491- pkcs11.NewAttribute(pkcs11.CKA_VALUE, metadataJSON),
492492- pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), // Store persistently
493493- pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), // Require authentication
494494- pkcs11.NewAttribute(pkcs11.CKA_MODIFIABLE, true), // Allow updates
495495- }
496496-497497- // Add OID if we successfully encoded it
498498- if derOID != nil {
499499- template = append(template, pkcs11.NewAttribute(pkcs11.CKA_OBJECT_ID, derOID))
500500- }
501501-502502- // Create the metadata object
503503- obj, err := c.ctx.CreateObject(c.session, template)
278278+ // Create the metadata object via helper
279279+ handle, err := createObjectPKCS11(c.session, metadataLabel, metadataJSON)
504280 if err != nil {
505281 return fmt.Errorf("failed to create metadata object: %w", err)
506282 }
507283508284 // Cache the metadata object handle
509509- c.dataObjects[metadataLabel] = obj
285285+ c.dataObjects[metadataLabel] = handle
510286511287 c.logger.V(2).Info("Created metadata object", "path", path, "label", metadataLabel)
512288 return nil
···524300 metadataLabel := path + metadataKeySuffix
525301526302 // Find the metadata object
527527- template := []*pkcs11.Attribute{
528528- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
529529- pkcs11.NewAttribute(pkcs11.CKA_LABEL, metadataLabel),
530530- }
531531-532532- if err := c.ctx.FindObjectsInit(c.session, template); err != nil {
533533- return nil, fmt.Errorf("failed to initialize metadata search: %w", err)
534534- }
535535- defer func() {
536536- if finalErr := c.ctx.FindObjectsFinal(c.session); finalErr != nil {
537537- c.logger.V(1).Info("Failed to finalize metadata search", "error", finalErr)
538538- }
539539- }()
540540-541541- objs, _, err := c.ctx.FindObjects(c.session, 1)
303303+ objects, err := findObjectsPKCS11(c.session, metadataLabel)
542304 if err != nil {
543305 return nil, fmt.Errorf("failed to find metadata object: %w", err)
544306 }
545307546546- if len(objs) == 0 {
547547- return nil, fmt.Errorf("metadata not found for path: %s", path)
548548- }
549549-550550- // Get the metadata value
551551- valueAttr, err := c.ctx.GetAttributeValue(c.session, objs[0], []*pkcs11.Attribute{
552552- pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
553553- })
554554- if err != nil {
555555- return nil, fmt.Errorf("failed to get metadata value: %w", err)
556556- }
557557-558558- if len(valueAttr) == 0 || len(valueAttr[0].Value) == 0 {
559559- return nil, fmt.Errorf("metadata object has no value")
560560- }
561561-562562- // Parse the JSON metadata
563563- var metadata SecretMetadata
564564- if err := json.Unmarshal(valueAttr[0].Value, &metadata); err != nil {
565565- return nil, fmt.Errorf("failed to parse metadata JSON: %w", err)
566566- }
567567-568568- return &metadata, nil
569569-}
570570-571571-// deleteSecretObjects removes all data objects matching the given path prefix
572572-func (c *PKCS11Client) deleteSecretObjects(path string) error {
573573- // Find all data objects (we'll filter by label after)
574574- template := []*pkcs11.Attribute{
575575- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
576576- }
577577-578578- if err := c.ctx.FindObjectsInit(c.session, template); err != nil {
579579- return fmt.Errorf("failed to initialize object search: %w", err)
580580- }
581581- defer func() {
582582- if finalErr := c.ctx.FindObjectsFinal(c.session); finalErr != nil {
583583- c.logger.V(1).Info("Failed to finalize object search", "error", finalErr)
584584- }
585585- }()
586586-587587- // Get all matching objects
588588- objs, _, err := c.ctx.FindObjects(c.session, 100) // Max 100 objects
589589- if err != nil {
590590- return fmt.Errorf("failed to find objects: %w", err)
591591- }
592592-593593- // Delete each object that matches our path
594594- for _, obj := range objs {
595595- // Get the label to check if this object matches our path
596596- labelAttr, err := c.ctx.GetAttributeValue(c.session, obj, []*pkcs11.Attribute{
597597- pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
598598- })
599599- if err != nil {
600600- c.logger.V(1).Info("Failed to get object label for deletion", "error", err)
601601- continue
602602- }
603603-604604- if len(labelAttr) == 0 || len(labelAttr[0].Value) == 0 {
605605- continue
606606- }
607607-608608- label := string(labelAttr[0].Value)
609609- // Only delete objects that match our path
610610- if !strings.HasPrefix(label, path) {
611611- continue
612612- }
613613-614614- if err := c.ctx.DestroyObject(c.session, obj); err != nil {
615615- c.logger.V(1).Info("Failed to delete object", "object", obj, "error", err)
616616- continue
617617- }
618618-619619- // Remove from cache
620620- for label, cachedObj := range c.dataObjects {
621621- if cachedObj == obj {
622622- delete(c.dataObjects, label)
623623- break
308308+ // Look for exact match
309309+ for _, obj := range objects {
310310+ if obj.Label == metadataLabel {
311311+ // Parse the JSON metadata
312312+ var metadata SecretMetadata
313313+ if err := json.Unmarshal(obj.Value, &metadata); err != nil {
314314+ return nil, fmt.Errorf("failed to parse metadata JSON: %w", err)
624315 }
316316+ return &metadata, nil
625317 }
626318 }
627319628628- return nil
320320+ return nil, fmt.Errorf("metadata not found for path: %s", path)
629321}
630322631323// DeleteSecret removes secret data from the specified HSM path
···639331640332 c.logger.Info("Deleting secret from HSM", "path", path)
641333642642- if err := c.deleteSecretObjects(path); err != nil {
334334+ if err := deleteSecretObjectsPKCS11(c.session, path); err != nil {
643335 return fmt.Errorf("failed to delete secret objects: %w", err)
644336 }
645337338338+ // Remove from cache
339339+ for label := range c.dataObjects {
340340+ if strings.HasPrefix(label, path) {
341341+ delete(c.dataObjects, label)
342342+ }
343343+ }
344344+646345 c.logger.Info("Successfully deleted secret from HSM", "path", path)
647346 return nil
648347}
···658357659358 c.logger.V(1).Info("Listing secrets from HSM", "prefix", prefix)
660359661661- // Find all data objects (we'll filter by prefix after)
662662- template := []*pkcs11.Attribute{
663663- pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_DATA),
664664- }
665665-666666- if err := c.ctx.FindObjectsInit(c.session, template); err != nil {
667667- return nil, fmt.Errorf("failed to initialize object search: %w", err)
668668- }
669669- defer func() {
670670- if finalErr := c.ctx.FindObjectsFinal(c.session); finalErr != nil {
671671- c.logger.V(1).Info("Failed to finalize object search", "error", finalErr)
672672- }
673673- }()
674674-675675- // Get all matching objects
676676- objs, _, err := c.ctx.FindObjects(c.session, 1000) // Max 1000 objects
360360+ // Find all data objects
361361+ objects, err := findObjectsPKCS11(c.session, "")
677362 if err != nil {
678363 return nil, fmt.Errorf("failed to find objects: %w", err)
679364 }
680365681366 // Extract unique paths from object labels
682367 pathsMap := make(map[string]bool)
683683- for _, obj := range objs {
684684- // Get the label
685685- labelAttr, err := c.ctx.GetAttributeValue(c.session, obj, []*pkcs11.Attribute{
686686- pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
687687- })
688688- if err != nil {
689689- c.logger.V(1).Info("Failed to get object label", "error", err)
690690- continue
691691- }
692692-693693- if len(labelAttr) == 0 || len(labelAttr[0].Value) == 0 {
694694- continue
695695- }
696696-697697- label := string(labelAttr[0].Value)
368368+ for _, obj := range objects {
369369+ label := obj.Label
698370699371 // Skip metadata objects when listing secrets
700372 if strings.HasSuffix(label, metadataKeySuffix) {
···774446 return fmt.Errorf("new PIN must be different from old PIN")
775447 }
776448777777- // Use PKCS#11 SetPIN function to change the PIN
778778- // Note: This changes the user PIN (not SO PIN)
779779- if err := c.ctx.SetPIN(c.session, oldPIN, newPIN); err != nil {
449449+ // Call platform-specific PIN change
450450+ if err := changePINPKCS11(c.session, oldPIN, newPIN); err != nil {
780451 c.logger.Error(err, "Failed to change HSM PIN")
781452 return fmt.Errorf("failed to change HSM PIN: %w", err)
782453 }
-90
internal/hsm/pkcs11_client_nocgo.go
···11-//go:build !cgo
22-// +build !cgo
33-44-/*
55-Copyright 2025.
66-77-Licensed under the Apache License, Version 2.0 (the "License");
88-you may not use this file except in compliance with the License.
99-You may obtain a copy of the License at
1010-1111- http://www.apache.org/licenses/LICENSE-2.0
1212-1313-Unless required by applicable law or agreed to in writing, software
1414-distributed under the License is distributed on an "AS IS" BASIS,
1515-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1616-See the License for the specific language governing permissions and
1717-limitations under the License.
1818-*/
1919-2020-package hsm
2121-2222-import (
2323- "context"
2424- "fmt"
2525-2626- "github.com/go-logr/logr"
2727- ctrl "sigs.k8s.io/controller-runtime"
2828-)
2929-3030-// PKCS11Client stub implementation when CGO is disabled
3131-type PKCS11Client struct {
3232- logger logr.Logger
3333-}
3434-3535-// NewPKCS11Client creates a new PKCS#11 HSM client stub
3636-func NewPKCS11Client() *PKCS11Client {
3737- return &PKCS11Client{
3838- logger: ctrl.Log.WithName("hsm-pkcs11-client-nocgo"),
3939- }
4040-}
4141-4242-// Initialize returns an error indicating PKCS#11 is not available
4343-func (c *PKCS11Client) Initialize(ctx context.Context, config Config) error {
4444- return fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
4545-}
4646-4747-// Close is a no-op for the stub implementation
4848-func (c *PKCS11Client) Close() error {
4949- return nil
5050-}
5151-5252-// GetInfo returns an error indicating PKCS#11 is not available
5353-func (c *PKCS11Client) GetInfo(ctx context.Context) (*HSMInfo, error) {
5454- return nil, fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
5555-}
5656-5757-// ReadSecret returns an error indicating PKCS#11 is not available
5858-func (c *PKCS11Client) ReadSecret(ctx context.Context, path string) (SecretData, error) {
5959- return nil, fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
6060-}
6161-6262-// WriteSecret returns an error indicating PKCS#11 is not available
6363-func (c *PKCS11Client) WriteSecret(ctx context.Context, path string, data SecretData, metadata *SecretMetadata) error {
6464- return fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
6565-}
6666-6767-// DeleteSecret returns an error indicating PKCS#11 is not available
6868-func (c *PKCS11Client) DeleteSecret(ctx context.Context, path string) error {
6969- return fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
7070-}
7171-7272-// ListSecrets returns an error indicating PKCS#11 is not available
7373-func (c *PKCS11Client) ListSecrets(ctx context.Context, prefix string) ([]string, error) {
7474- return nil, fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
7575-}
7676-7777-// GetChecksum returns an error indicating PKCS#11 is not available
7878-func (c *PKCS11Client) GetChecksum(ctx context.Context, path string) (string, error) {
7979- return "", fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
8080-}
8181-8282-// IsConnected always returns false for the stub implementation
8383-func (c *PKCS11Client) IsConnected() bool {
8484- return false
8585-}
8686-8787-// WithRetry returns an error indicating PKCS#11 is not available
8888-func (c *PKCS11Client) WithRetry(ctx context.Context, operation func() error) error {
8989- return fmt.Errorf("PKCS#11 support not available: binary was built without CGO")
9090-}
+67
internal/hsm/pkcs11_stub.go
···11+//go:build !cgo
22+33+/*
44+Copyright 2025.
55+66+Licensed under the Apache License, Version 2.0 (the "License");
77+you may not use this file except in compliance with the License.
88+You may obtain a copy of the License at
99+1010+ http://www.apache.org/licenses/LICENSE-2.0
1111+1212+Unless required by applicable law or agreed to in writing, software
1313+distributed under the License is distributed on an "AS IS" BASIS,
1414+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+See the License for the specific language governing permissions and
1616+limitations under the License.
1717+*/
1818+1919+package hsm
2020+2121+import (
2222+ "fmt"
2323+)
2424+2525+// Stub types for non-CGO builds
2626+type Session struct{}
2727+type ObjectHandle struct{}
2828+2929+// initializePKCS11 returns an error for non-CGO builds
3030+func initializePKCS11(config Config, pin string) (*Session, uint, error) {
3131+ return nil, 0, fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
3232+}
3333+3434+// closePKCS11 is a no-op for non-CGO builds
3535+func closePKCS11(session *Session) error {
3636+ return nil
3737+}
3838+3939+// getTokenInfoPKCS11 returns an error for non-CGO builds
4040+func getTokenInfoPKCS11(session *Session, slot uint) (*tokenInfo, error) {
4141+ return nil, fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
4242+}
4343+4444+// findObjectsPKCS11 returns an error for non-CGO builds
4545+func findObjectsPKCS11(session *Session, path string) ([]pkcs11Object, error) {
4646+ return nil, fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
4747+}
4848+4949+// createObjectPKCS11 returns an error for non-CGO builds
5050+func createObjectPKCS11(session *Session, label string, value []byte) (ObjectHandle, error) {
5151+ return ObjectHandle{}, fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
5252+}
5353+5454+// deleteSecretObjectsPKCS11 returns an error for non-CGO builds
5555+func deleteSecretObjectsPKCS11(session *Session, path string) error {
5656+ return fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
5757+}
5858+5959+// changePINPKCS11 returns an error for non-CGO builds
6060+func changePINPKCS11(session *Session, oldPIN, newPIN string) error {
6161+ return fmt.Errorf("PKCS#11 support requires CGO (set CGO_ENABLED=1 and rebuild)")
6262+}
6363+6464+// init logs a warning about CGO requirement
6565+func init() {
6666+ fmt.Println("WARNING: PKCS#11 client running in fallback mode (CGO disabled). HSM operations will fail.")
6767+}