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.

chore: bump version to 0.6.17

+145 -57
+61 -28
Dockerfile
··· 1 - # Production Dockerfile with real PKCS#11 support 2 - # Build the manager and agent binaries with CGO enabled 3 - FROM golang:1.24-alpine AS builder 1 + # Multi-stage distroless Dockerfile for maximum security with USB access 2 + # Phase 2: Root + Distroless - compensates for root requirement with minimal attack surface 3 + 4 + # Stage 1: Go builder (also serves as dependency source) 5 + FROM golang:1.24-bookworm AS builder 4 6 ARG TARGETOS 5 7 ARG TARGETARCH 6 8 7 - # Install build dependencies for PKCS#11 and USB event monitoring 8 - RUN apk add --no-cache \ 9 - gcc \ 10 - g++ \ 11 - eudev-dev \ 12 - linux-headers 9 + # Install both runtime and development packages in single stage 10 + RUN apt-get update && apt-get install -y \ 11 + opensc \ 12 + pcscd \ 13 + libpcsclite1 \ 14 + libusb-1.0-0 \ 15 + udev \ 16 + ca-certificates \ 17 + libudev-dev \ 18 + libpcsclite-dev \ 19 + libusb-1.0-0-dev \ 20 + && rm -rf /var/lib/apt/lists/* 21 + 22 + # Create necessary runtime directories 23 + RUN mkdir -p /var/run/pcscd /var/lock/pcsc && \ 24 + chmod 755 /var/run/pcscd /var/lock/pcsc 13 25 14 - # Return to workspace for Go builds 15 26 WORKDIR /workspace 16 - 17 27 # Copy the Go Modules manifests 18 28 COPY go.mod go.mod 19 29 COPY go.sum go.sum ··· 21 31 # and so that source changes don't invalidate our downloaded layer 22 32 RUN go mod download 23 33 34 + # Copy the go source 24 35 COPY cmd/ cmd/ 25 36 COPY api/ api/ 26 37 COPY internal/ internal/ 27 38 COPY web/ web/ 28 39 29 - # Build unified binary with CGO enabled for PKCS#11 support (agent mode needs it) 40 + # Build with CGO enabled for PKCS#11 support 30 41 RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o hsm-operator cmd/hsm-operator/main.go 31 42 32 - FROM alpine:3.22 33 - RUN apk add --no-cache opensc-dev ccid pcsc-lite openssl libtool libusb ca-certificates eudev 43 + # Stage 2: Base runtime stage with all files 44 + FROM gcr.io/distroless/cc-debian12:debug AS runtime 45 + 46 + # Copy essential system files and create nonroot user 47 + COPY --from=builder /etc/passwd /etc/passwd 48 + COPY --from=builder /etc/group /etc/group 49 + 50 + # Ensure nonroot user exists (distroless provides user 65532:65532) 51 + 52 + # Copy PKCS#11 and USB libraries 53 + COPY --from=builder /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so /usr/lib/pkcs11/opensc-pkcs11.so 54 + COPY --from=builder /usr/lib/x86_64-linux-gnu/libpcsclite.so.1 /usr/lib/x86_64-linux-gnu/ 55 + COPY --from=builder /usr/lib/x86_64-linux-gnu/libusb-1.0.so.0 /usr/lib/x86_64-linux-gnu/ 56 + COPY --from=builder /usr/lib/x86_64-linux-gnu/libudev.so.1 /usr/lib/x86_64-linux-gnu/ 57 + COPY --from=builder /usr/lib/x86_64-linux-gnu/libcap.so.2 /usr/lib/x86_64-linux-gnu/ 58 + COPY --from=builder /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 /usr/lib/x86_64-linux-gnu/ 34 59 35 - WORKDIR / 36 - COPY --from=builder /workspace/hsm-operator . 37 - COPY --from=builder /workspace/web ./web/ 38 - COPY entrypoint.sh /entrypoint.sh 39 - RUN chmod +x /entrypoint.sh 60 + # Copy essential binaries 61 + COPY --from=builder /usr/sbin/pcscd /usr/sbin/ 62 + COPY --from=builder /usr/bin/pkcs11-tool /usr/bin/ 40 63 41 - # Create USB device access groups and add user to them 42 - RUN addgroup -g 20 dialout && \ 43 - adduser 65532 dialout && \ 44 - addgroup -g 85 usb 2>/dev/null || true && \ 45 - adduser 65532 usb 2>/dev/null || true 64 + # Copy udev rules for HSM devices (CCID support) 65 + COPY --from=builder /lib/udev/rules.d/92-libccid.rules /lib/udev/rules.d/ 46 66 47 - RUN mkdir -p /var/run/pcscd /var/lock/pcsc && \ 48 - chown -R 65532:65532 /var/run/pcscd /var/lock/pcsc && \ 49 - chmod 755 /var/run/pcscd /var/lock/pcsc 67 + # Copy CA certificates 68 + COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 69 + 70 + # Copy runtime directories 71 + COPY --from=builder /var/run/pcscd /var/run/pcscd 72 + COPY --from=builder /var/lock/pcsc /var/lock/pcsc 50 73 74 + # Copy application binary and entrypoint 75 + COPY --from=builder /workspace/hsm-operator /hsm-operator 76 + COPY --chmod=755 entrypoint.sh /entrypoint.sh 77 + 78 + # Default to distroless nonroot user (can be overridden by deployment securityContext) 51 79 USER 65532:65532 52 80 53 - ENTRYPOINT ["/entrypoint.sh"] 81 + # Stage 3: Debug variant with shell (DEFAULT for testing) 82 + FROM runtime 83 + 84 + # Debug image includes busybox shell for troubleshooting 85 + # Access via: kubectl exec -it pod -- /busybox/sh 86 + ENTRYPOINT ["/busybox/sh", "/entrypoint.sh"]
+53
Dockerfile.alpine
··· 1 + # Production Dockerfile with real PKCS#11 support 2 + # Build the manager and agent binaries with CGO enabled 3 + FROM golang:1.24-alpine AS builder 4 + ARG TARGETOS 5 + ARG TARGETARCH 6 + 7 + # Install build dependencies for PKCS#11 and USB event monitoring 8 + RUN apk add --no-cache \ 9 + gcc \ 10 + g++ \ 11 + eudev-dev \ 12 + linux-headers 13 + 14 + # Return to workspace for Go builds 15 + WORKDIR /workspace 16 + 17 + # Copy the Go Modules manifests 18 + COPY go.mod go.mod 19 + COPY go.sum go.sum 20 + # cache deps before building and copying source so that we don't need to re-download as much 21 + # and so that source changes don't invalidate our downloaded layer 22 + RUN go mod download 23 + 24 + COPY cmd/ cmd/ 25 + COPY api/ api/ 26 + COPY internal/ internal/ 27 + COPY web/ web/ 28 + 29 + # Build unified binary with CGO enabled for PKCS#11 support (agent mode needs it) 30 + RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o hsm-operator cmd/hsm-operator/main.go 31 + 32 + FROM alpine:3.22 33 + RUN apk add --no-cache opensc-dev ccid pcsc-lite openssl libtool libusb ca-certificates eudev 34 + 35 + WORKDIR / 36 + COPY --from=builder /workspace/hsm-operator . 37 + COPY --from=builder /workspace/web ./web/ 38 + COPY entrypoint.sh /entrypoint.sh 39 + RUN chmod +x /entrypoint.sh 40 + 41 + # Create USB device access groups and add user to them 42 + RUN addgroup -g 20 dialout && \ 43 + adduser 65532 dialout && \ 44 + addgroup -g 85 usb 2>/dev/null || true && \ 45 + adduser 65532 usb 2>/dev/null || true 46 + 47 + RUN mkdir -p /var/run/pcscd /var/lock/pcsc && \ 48 + chown -R 65532:65532 /var/run/pcscd /var/lock/pcsc && \ 49 + chmod 755 /var/run/pcscd /var/lock/pcsc 50 + 51 + USER 65532:65532 52 + 53 + ENTRYPOINT ["/entrypoint.sh"]
+10 -2
Makefile
··· 3 3 # To re-generate a bundle for another specific version without changing the standard setup, you can: 4 4 # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 5 5 # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 6 - VERSION ?= 0.6.16 6 + VERSION ?= 0.6.17 7 7 8 8 # CHANNELS define the bundle channels used in the bundle. 9 9 # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") ··· 237 237 # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. 238 238 # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 239 239 .PHONY: docker-build 240 - docker-build: ## Build production docker image with PKCS#11 support. 240 + docker-build: ## Build debug distroless image (root + busybox shell) - default for testing. 241 241 $(CONTAINER_TOOL) build -t ${IMG} . 242 + 243 + .PHONY: docker-build-production 244 + docker-build-production: ## Build production distroless image (root + no shell). 245 + $(CONTAINER_TOOL) build --target production -t ${IMG}-production . 246 + 247 + .PHONY: docker-build-alpine 248 + docker-build-alpine: ## Build Alpine-based image (fallback option). 249 + $(CONTAINER_TOOL) build -f Dockerfile.alpine -t ${IMG}-alpine . 242 250 243 251 .PHONY: docker-push 244 252 docker-push: ## Push docker image with the manager.
+1 -3
config/manager/manager.yaml
··· 58 58 seccompProfile: 59 59 type: RuntimeDefault 60 60 containers: 61 - - command: 62 - - /entrypoint.sh 63 - args: 61 + - args: 64 62 - --mode=manager 65 63 - --leader-elect 66 64 - --health-probe-bind-address=:8081
+1 -1
entrypoint.sh
··· 1 - #!/bin/sh 2 1 set -e 3 2 4 3 # Debug: Show user and USB device permissions for agent mode only ··· 23 22 fi 24 23 25 24 # Start pcscd with debug output 25 + # Use /tmp for runtime files if root filesystem is readonly 26 26 echo "Starting pcscd..." 27 27 pcscd -f -d -a & 28 28 PCSCD_PID=$!
+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.6.16 6 - appVersion: v0.6.16 5 + version: 0.6.17 6 + appVersion: v0.6.17 7 7 icon: https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/icon/color/kubernetes-icon-color.svg 8 8 home: https://github.com/evanjarrett/hsm-secrets-operator 9 9 sources:
+1 -1
internal/controller/discovery_daemonset_controller.go
··· 206 206 { 207 207 Name: "discovery", 208 208 Image: discoveryImage, 209 - Command: []string{"/entrypoint.sh", "discovery"}, 209 + Args: []string{"--mode", "discovery"}, 210 210 Env: []corev1.EnvVar{ 211 211 { 212 212 Name: "NODE_NAME",
+15 -19
internal/controller/hsmpool_agent_controller.go
··· 543 543 } 544 544 545 545 var replicas int32 = 1 546 - // var rootUserId int64 = 0 547 - var pcscdUserId int64 = 65532 548 - var pcscdGroupId int64 = 65532 546 + var rootUserId int64 = 0 547 + // Fallback to root for USB device access - compensated by distroless 549 548 falsePtr := new(bool) 550 549 *falsePtr = false 551 550 truePtr := new(bool) ··· 611 610 }, 612 611 }, 613 612 SecurityContext: &corev1.PodSecurityContext{ 614 - RunAsUser: &pcscdUserId, 615 - RunAsGroup: &pcscdGroupId, 616 - RunAsNonRoot: truePtr, 613 + RunAsUser: &rootUserId, 614 + RunAsGroup: &rootUserId, 615 + RunAsNonRoot: falsePtr, // Root required for USB access 617 616 }, 618 617 ServiceAccountName: r.ServiceAccountName, 619 618 Containers: []corev1.Container{ 620 619 { 621 620 Name: "agent", 622 621 Image: agentImage, 623 - Command: []string{ 624 - "/entrypoint.sh", 625 - "agent", 626 - }, 627 622 Args: r.buildAgentArgs(ctx, hsmPool, deviceName), 628 623 Env: []corev1.EnvVar{}, 629 624 Ports: []corev1.ContainerPort{ ··· 669 664 }, 670 665 }, 671 666 SecurityContext: &corev1.SecurityContext{ 672 - Privileged: falsePtr, 673 - AllowPrivilegeEscalation: falsePtr, 674 - ReadOnlyRootFilesystem: falsePtr, 675 - RunAsNonRoot: truePtr, 676 - RunAsUser: &pcscdUserId, 667 + Privileged: falsePtr, // Still no privileged containers 668 + AllowPrivilegeEscalation: falsePtr, // Still no privilege escalation 669 + ReadOnlyRootFilesystem: truePtr, // Possible with distroless 670 + RunAsNonRoot: falsePtr, // Root required for USB 671 + RunAsUser: &rootUserId, 677 672 Capabilities: &corev1.Capabilities{ 678 - Drop: []corev1.Capability{"ALL"}, 679 - Add: []corev1.Capability{ 680 - "DAC_OVERRIDE", // Allow bypassing file permission checks for USB devices 681 - }, 673 + Drop: []corev1.Capability{"ALL"}, // Drop all capabilities 674 + // No additional capabilities needed with root 682 675 }, 683 676 SeccompProfile: &corev1.SeccompProfile{ 684 677 Type: corev1.SeccompProfileTypeRuntimeDefault, ··· 688 681 { 689 682 Name: "tmp", 690 683 MountPath: "/tmp", 684 + ReadOnly: false, // Required for pcscd runtime with readonly filesystem 691 685 }, 692 686 { 693 687 Name: "usb-bus", ··· 697 691 { 698 692 Name: "pcscd-run", 699 693 MountPath: "/var/run/pcscd", 694 + ReadOnly: false, // Required for pcscd socket 700 695 }, 701 696 }, 702 697 }, ··· 817 812 // buildAgentArgs builds CLI arguments for the HSM agent 818 813 func (r *HSMPoolAgentReconciler) buildAgentArgs(ctx context.Context, hsmPool *hsmv1alpha1.HSMPool, deviceName string) []string { 819 814 args := []string{ 815 + "--mode=agent", 820 816 "--device-name=" + deviceName, 821 817 "--port=" + fmt.Sprintf("%d", AgentPort), 822 818 "--health-port=" + fmt.Sprintf("%d", AgentHealthPort),
+1 -1
test/e2e/e2e_test.go
··· 156 156 } 157 157 158 158 By("Fetching curl-metrics logs") 159 - cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace, "--ignore-not-found=true") 159 + cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) 160 160 metricsOutput, err := utils.Run(cmd) 161 161 if err == nil && metricsOutput != "" { 162 162 _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput)