Skip to content

Commit 64d856b

Browse files
committed
private registry support
1 parent f5f155e commit 64d856b

File tree

3 files changed

+84
-77
lines changed

3 files changed

+84
-77
lines changed

cmd/plugin/cli/mount.go

+14-16
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,55 @@ package cli
33
import (
44
"context"
55
"fmt"
6-
"os"
7-
"strconv"
8-
96
"github.com/fenio/pv-mounter/pkg/plugin"
107
"github.com/spf13/cobra"
8+
"os"
9+
"strconv"
1110
)
1211

1312
func mountCmd() *cobra.Command {
1413
var needsRoot bool
1514
var debug bool
16-
15+
var image string
16+
var imageSecret string
1717
cmd := &cobra.Command{
18-
Use: "mount [--needs-root] [--debug] <namespace> <pvc-name> <local-mount-point>",
18+
Use: "mount [--needs-root] [--debug] [--image] [--image-secret] <namespace> <pvc-name> <local-mount-point>",
1919
Short: "Mount a PVC to a local directory",
2020
Args: cobra.ExactArgs(3),
2121
RunE: func(cmd *cobra.Command, args []string) error {
22-
// Check for NEEDS_ROOT environment variable
2322
if needsRootEnv, exists := os.LookupEnv("NEEDS_ROOT"); exists {
24-
// Convert the environment variable to a boolean
2523
if parsedNeedsRoot, err := strconv.ParseBool(needsRootEnv); err == nil {
2624
needsRoot = parsedNeedsRoot
2725
} else {
2826
return fmt.Errorf("invalid value for NEEDS_ROOT: %v", needsRootEnv)
2927
}
3028
}
31-
32-
// Check for DEBUG environment variable
3329
if debugEnv, exists := os.LookupEnv("DEBUG"); exists {
34-
// Convert the environment variable to a boolean
3530
if parsedDebug, err := strconv.ParseBool(debugEnv); err == nil {
3631
debug = parsedDebug
3732
} else {
3833
return fmt.Errorf("invalid value for DEBUG: %v", debugEnv)
3934
}
4035
}
41-
36+
if imageEnv, exists := os.LookupEnv("IMAGE"); exists {
37+
image = imageEnv
38+
}
39+
if imageSecretEnv, exists := os.LookupEnv("IMAGE_SECRET"); exists {
40+
imageSecret = imageSecretEnv
41+
}
4242
namespace := args[0]
4343
pvcName := args[1]
4444
localMountPoint := args[2]
45-
46-
// Create a context
4745
ctx := context.Background()
48-
49-
if err := plugin.Mount(ctx, namespace, pvcName, localMountPoint, needsRoot, debug); err != nil {
46+
if err := plugin.Mount(ctx, namespace, pvcName, localMountPoint, needsRoot, debug, image, imageSecret); err != nil {
5047
return fmt.Errorf("failed to mount PVC: %w", err)
5148
}
5249
return nil
5350
},
5451
}
55-
5652
cmd.Flags().BoolVar(&needsRoot, "needs-root", false, "Mount the filesystem using the root account")
5753
cmd.Flags().BoolVar(&debug, "debug", false, "Enable debug mode to print additional information")
54+
cmd.Flags().StringVar(&image, "image", "", "Custom container image for the volume-exposer")
55+
cmd.Flags().StringVar(&imageSecret, "image-secret", "", "Kubernetes secret name for accessing private registry")
5856
return cmd
5957
}

pkg/plugin/mount.go

+69-60
Original file line numberDiff line numberDiff line change
@@ -36,35 +36,27 @@ const (
3636

3737
var DefaultID int64 = 2137
3838

39-
func Mount(ctx context.Context, namespace, pvcName, localMountPoint string, needsRoot, debug bool) error {
40-
39+
func Mount(ctx context.Context, namespace, pvcName, localMountPoint string, needsRoot, debug bool, image, imageSecret string) error {
4140
checkSSHFS()
42-
4341
if err := validateMountPoint(localMountPoint); err != nil {
4442
return err
4543
}
46-
4744
clientset, err := BuildKubeClient()
4845
if err != nil {
4946
return err
5047
}
51-
5248
pvc, err := checkPVCUsage(ctx, clientset, namespace, pvcName)
5349
if err != nil {
5450
return err
5551
}
56-
5752
canBeMounted, podUsingPVC, err := checkPVAccessMode(ctx, clientset, pvc, namespace)
5853
if err != nil {
5954
return err
6055
}
61-
6256
if canBeMounted {
63-
return handleRWX(ctx, clientset, namespace, pvcName, localMountPoint, needsRoot, debug)
57+
return handleRWX(ctx, clientset, namespace, pvcName, localMountPoint, needsRoot, debug, image, imageSecret)
6458
}
65-
66-
return handleRWO(ctx, clientset, namespace, pvcName, localMountPoint, podUsingPVC, needsRoot, debug)
67-
59+
return handleRWO(ctx, clientset, namespace, pvcName, localMountPoint, podUsingPVC, needsRoot, debug, image, imageSecret)
6860
}
6961

7062
func validateMountPoint(localMountPoint string) error {
@@ -74,90 +66,79 @@ func validateMountPoint(localMountPoint string) error {
7466
return nil
7567
}
7668

77-
func handleRWX(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, needsRoot bool, debug bool) error {
78-
69+
func handleRWX(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, needsRoot, debug bool, image, imageSecret string) error {
7970
privateKey, publicKey, err := GenerateKeyPair(elliptic.P256())
8071
if err != nil {
8172
return fmt.Errorf("error generating key pair: %v", err)
8273
}
83-
8474
if debug {
8575
fmt.Printf("Private Key:\n%s\n", privateKey)
8676
}
87-
88-
podName, port, err := setupPod(ctx, clientset, namespace, pvcName, publicKey, "standalone", DefaultSSHPort, "", needsRoot)
77+
podName, port, err := setupPod(ctx, clientset, namespace, pvcName, publicKey, "standalone", DefaultSSHPort, "", needsRoot, image, imageSecret)
8978
if err != nil {
9079
return err
9180
}
92-
9381
if err := waitForPodReady(ctx, clientset, namespace, podName); err != nil {
9482
return err
9583
}
96-
9784
if err := setupPortForwarding(namespace, podName, port); err != nil {
9885
return err
9986
}
100-
10187
return mountPVCOverSSH(port, localMountPoint, pvcName, privateKey, needsRoot)
10288
}
10389

104-
func handleRWO(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, podUsingPVC string, needsRoot bool, debug bool) error {
105-
90+
func handleRWO(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, podUsingPVC string, needsRoot, debug bool, image, imageSecret string) error {
10691
privateKey, publicKey, err := GenerateKeyPair(elliptic.P256())
10792
if err != nil {
10893
return fmt.Errorf("error generating key pair: %v", err)
10994
}
110-
11195
if debug {
11296
fmt.Printf("Private Key:\n%s\n", privateKey)
11397
}
114-
115-
podName, port, err := setupPod(ctx, clientset, namespace, pvcName, publicKey, "proxy", ProxySSHPort, podUsingPVC, needsRoot)
98+
podName, port, err := setupPod(ctx, clientset, namespace, pvcName, publicKey, "proxy", ProxySSHPort, podUsingPVC, needsRoot, image, imageSecret)
11699
if err != nil {
117100
return err
118101
}
119-
120102
if err := waitForPodReady(ctx, clientset, namespace, podName); err != nil {
121103
return err
122104
}
123-
124105
proxyPodIP, err := getPodIP(ctx, clientset, namespace, podName)
125106
if err != nil {
126107
return err
127108
}
128-
129-
if err := createEphemeralContainer(ctx, clientset, namespace, podUsingPVC, privateKey, publicKey, proxyPodIP, needsRoot); err != nil {
109+
if err := createEphemeralContainer(ctx, clientset, namespace, podUsingPVC, privateKey, publicKey, proxyPodIP, needsRoot, image); err != nil {
130110
return err
131111
}
132-
133112
if err := setupPortForwarding(namespace, podName, port); err != nil {
134113
return err
135114
}
136-
137115
return mountPVCOverSSH(port, localMountPoint, pvcName, privateKey, needsRoot)
138116
}
139117

140-
func createEphemeralContainer(ctx context.Context, clientset *kubernetes.Clientset, namespace, podName, privateKey, publicKey, proxyPodIP string, needsRoot bool) error {
141-
// Retrieve the existing pod to get the volume name
118+
func createEphemeralContainer(ctx context.Context, clientset *kubernetes.Clientset, namespace, podName, privateKey, publicKey, proxyPodIP string, needsRoot bool, image string) error {
142119
existingPod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
143120
if err != nil {
144121
return fmt.Errorf("failed to get existing pod: %v", err)
145122
}
146-
147123
volumeName, err := getPVCVolumeName(existingPod)
148124
if err != nil {
149125
return err
150126
}
151-
152127
ephemeralContainerName := fmt.Sprintf("volume-exposer-ephemeral-%s", randSeq(5))
153128
fmt.Printf("Adding ephemeral container %s to pod %s with volume name %s\n", ephemeralContainerName, podName, volumeName)
154-
155-
image, securityContext := getEphemeralContainerSettings(needsRoot)
156-
129+
imageToUse := image
130+
if imageToUse == "" {
131+
if needsRoot {
132+
imageToUse = PrivilegedImage
133+
} else {
134+
imageToUse = Image
135+
}
136+
}
137+
securityContext := getSecurityContext(needsRoot)
157138
ephemeralContainer := corev1.EphemeralContainer{
158139
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
159140
Name: ephemeralContainerName,
160-
Image: image,
141+
Image: imageToUse,
161142
ImagePullPolicy: corev1.PullAlways,
162143
Env: []corev1.EnvVar{
163144
{Name: "ROLE", Value: "ephemeral"},
@@ -175,7 +156,6 @@ func createEphemeralContainer(ctx context.Context, clientset *kubernetes.Clients
175156
},
176157
},
177158
}
178-
179159
patchData, err := json.Marshal(map[string]interface{}{
180160
"spec": map[string]interface{}{
181161
"ephemeralContainers": []corev1.EphemeralContainer{ephemeralContainer},
@@ -184,12 +164,10 @@ func createEphemeralContainer(ctx context.Context, clientset *kubernetes.Clients
184164
if err != nil {
185165
return fmt.Errorf("failed to marshal ephemeral container spec: %v", err)
186166
}
187-
188167
_, err = clientset.CoreV1().Pods(namespace).Patch(ctx, podName, types.StrategicMergePatchType, patchData, metav1.PatchOptions{}, "ephemeralcontainers")
189168
if err != nil {
190169
return fmt.Errorf("failed to patch pod with ephemeral container: %v", err)
191170
}
192-
193171
fmt.Printf("Successfully added ephemeral container %s to pod %s\n", ephemeralContainerName, podName)
194172
return nil
195173
}
@@ -245,9 +223,9 @@ func checkPVCUsage(ctx context.Context, clientset *kubernetes.Clientset, namespa
245223
return pvc, nil
246224
}
247225

248-
func setupPod(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, publicKey, role string, sshPort int, originalPodName string, needsRoot bool) (string, int, error) {
226+
func setupPod(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, publicKey, role string, sshPort int, originalPodName string, needsRoot bool, image, imageSecret string) (string, int, error) {
249227
podName, port := generatePodNameAndPort(role)
250-
pod := createPodSpec(podName, port, pvcName, publicKey, role, sshPort, originalPodName, needsRoot)
228+
pod := createPodSpec(podName, port, pvcName, publicKey, role, sshPort, originalPodName, needsRoot, image, imageSecret)
251229
if _, err := clientset.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}); err != nil {
252230
return "", 0, fmt.Errorf("failed to create pod: %v", err)
253231
}
@@ -339,35 +317,37 @@ func generatePodNameAndPort(role string) (string, int) {
339317
return podName, port
340318
}
341319

342-
func createPodSpec(podName string, port int, pvcName, publicKey, role string, sshPort int, originalPodName string, needsRoot bool) *corev1.Pod {
343-
320+
func createPodSpec(podName string, port int, pvcName, publicKey, role string, sshPort int, originalPodName string, needsRoot bool, image, imageSecret string) *corev1.Pod {
344321
envVars := []corev1.EnvVar{
345322
{Name: "SSH_PUBLIC_KEY", Value: publicKey},
346323
{Name: "SSH_PORT", Value: fmt.Sprintf("%d", sshPort)},
347324
{Name: "NEEDS_ROOT", Value: fmt.Sprintf("%v", needsRoot)},
348325
}
349-
350-
// Add the ROLE environment variable if the role is "standalone" or "proxy"
351326
if role == "standalone" || role == "proxy" {
352327
envVars = append(envVars, corev1.EnvVar{
353328
Name: "ROLE",
354329
Value: role,
355330
})
356331
}
357-
358-
image, securityContext := getEphemeralContainerSettings(needsRoot)
359-
332+
imageToUse := image
333+
if imageToUse == "" {
334+
if needsRoot {
335+
imageToUse = PrivilegedImage
336+
} else {
337+
imageToUse = Image
338+
}
339+
}
340+
securityContext := getSecurityContext(needsRoot)
360341
runAsNonRoot := !needsRoot
361342
runAsUser := int64(DefaultUserGroup)
362343
runAsGroup := int64(DefaultUserGroup)
363344
if needsRoot {
364345
runAsUser = 0
365346
runAsGroup = 0
366347
}
367-
368348
container := corev1.Container{
369349
Name: "volume-exposer",
370-
Image: image,
350+
Image: imageToUse,
371351
ImagePullPolicy: corev1.PullAlways,
372352
Ports: []corev1.ContainerPort{
373353
{ContainerPort: int32(sshPort)},
@@ -386,18 +366,18 @@ func createPodSpec(podName string, port int, pvcName, publicKey, role string, ss
386366
},
387367
},
388368
}
389-
390369
labels := map[string]string{
391370
"app": "volume-exposer",
392371
"pvcName": pvcName,
393372
"portNumber": fmt.Sprintf("%d", port),
394373
}
395-
396-
// Add the original pod name label if provided
397374
if originalPodName != "" {
398375
labels["originalPodName"] = originalPodName
399376
}
400-
377+
imagePullSecrets := []corev1.LocalObjectReference{}
378+
if imageSecret != "" {
379+
imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{Name: imageSecret})
380+
}
401381
podSpec := &corev1.Pod{
402382
ObjectMeta: metav1.ObjectMeta{
403383
Name: podName,
@@ -410,10 +390,9 @@ func createPodSpec(podName string, port int, pvcName, publicKey, role string, ss
410390
RunAsUser: &runAsUser,
411391
RunAsGroup: &runAsGroup,
412392
},
393+
ImagePullSecrets: imagePullSecrets,
413394
},
414395
}
415-
416-
// Only mount the volume if the role is not "proxy"
417396
if role != "proxy" {
418397
container.VolumeMounts = []corev1.VolumeMount{
419398
{MountPath: "/volume", Name: "my-pvc"},
@@ -428,10 +407,8 @@ func createPodSpec(podName string, port int, pvcName, publicKey, role string, ss
428407
},
429408
},
430409
}
431-
// Update the container in the podSpec with the volume mounts
432410
podSpec.Spec.Containers[0] = container
433411
}
434-
435412
return podSpec
436413
}
437414

@@ -484,3 +461,35 @@ func getEphemeralContainerSettings(needsRoot bool) (string, *corev1.SecurityCont
484461
}
485462
return image, securityContext
486463
}
464+
465+
func getSecurityContext(needsRoot bool) *corev1.SecurityContext {
466+
allowPrivilegeEscalationTrue := true
467+
allowPrivilegeEscalationFalse := false
468+
readOnlyRootFilesystemTrue := true
469+
runAsNonRootTrue := true
470+
seccompProfileRuntimeDefault := corev1.SeccompProfile{
471+
Type: corev1.SeccompProfileTypeRuntimeDefault,
472+
}
473+
if needsRoot {
474+
return &corev1.SecurityContext{
475+
AllowPrivilegeEscalation: &allowPrivilegeEscalationTrue,
476+
ReadOnlyRootFilesystem: &readOnlyRootFilesystemTrue,
477+
Capabilities: &corev1.Capabilities{
478+
Add: []corev1.Capability{"SYS_ADMIN", "SYS_CHROOT"},
479+
},
480+
SeccompProfile: &seccompProfileRuntimeDefault,
481+
}
482+
} else {
483+
return &corev1.SecurityContext{
484+
AllowPrivilegeEscalation: &allowPrivilegeEscalationFalse,
485+
ReadOnlyRootFilesystem: &readOnlyRootFilesystemTrue,
486+
Capabilities: &corev1.Capabilities{
487+
Drop: []corev1.Capability{"ALL"},
488+
},
489+
SeccompProfile: &seccompProfileRuntimeDefault,
490+
RunAsUser: &DefaultID,
491+
RunAsGroup: &DefaultID,
492+
RunAsNonRoot: &runAsNonRootTrue,
493+
}
494+
}
495+
}

pkg/plugin/mount_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func TestGeneratePodNameAndPort(t *testing.T) {
151151
}
152152

153153
func TestCreatePodSpec(t *testing.T) {
154-
podSpec := createPodSpec("test-pod", 12345, "test-pvc", "publicKey", "standalone", 22, "", false)
154+
podSpec := createPodSpec("test-pod", 12345, "test-pvc", "publicKey", "standalone", 22, "", false, "whatever", "secret")
155155
if podSpec.Name != "test-pod" {
156156
t.Errorf("Expected pod name 'test-pod', got '%s'", podSpec.Name)
157157
}

0 commit comments

Comments
 (0)