Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotation for setting access mode on automounted configmap/secret files #6750

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ Mounting resources can be additionally configured via annotations:

- `devfile.io/read-only`: for persistent volume claims, mount the resource as read-only

- `devfile.io/mount-access-mode`: for secret/configmap, can be used to configure file permissions on mounted files
Copy link
Member

@rm3l rm3l Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also provide an example value? Along with the valid range?

This makes me wonder that we could add a complete example of an auto-mounting resource, with its labels and annotations. That would be helpful and easier to understand, IMO. But we can do that in a separate PR if you prefer..

LGTM otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed the doc, and added a test for decimal notation.

I would prefer we discuss which kind of example we want and where to place it (blog, etc), and to create a separate PR for this.

2 changes: 2 additions & 0 deletions pkg/configAutomount/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type AutomountInfo struct {
ReadOnly bool
// Keys defines the list of keys to mount when MountAs is Subpath
Keys []string
// MountAccessMode indicates the access mode for configmap and secret mounted as files
MountAccessMode *int32
}

type Client interface {
Expand Down
65 changes: 50 additions & 15 deletions pkg/configAutomount/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package configAutomount

import (
"fmt"
"path/filepath"
"sort"
"strconv"

"github.com/redhat-developer/odo/pkg/kclient"
)
Expand All @@ -11,9 +13,10 @@ const (
labelMountName = "devfile.io/auto-mount"
labelMountValue = "true"

annotationMountPathName = "devfile.io/mount-path"
annotationMountAsName = "devfile.io/mount-as"
annotationReadOnlyName = "devfile.io/read-only"
annotationMountPathName = "devfile.io/mount-path"
annotationMountAsName = "devfile.io/mount-as"
annotationReadOnlyName = "devfile.io/read-only"
annotationMountAccessMode = "devfile.io/mount-access-mode"
)

type KubernetesClient struct {
Expand Down Expand Up @@ -96,13 +99,20 @@ func (o KubernetesClient) getAutomountingSecrets() ([]AutomountInfo, error) {
}
sort.Strings(keys)
}

mountAccessMode, err := getMountAccessModeFromAnnotation(secret.Annotations)
if err != nil {
return nil, err
}

result = append(result, AutomountInfo{
VolumeType: VolumeTypeSecret,
VolumeName: secret.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: secret.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
VolumeType: VolumeTypeSecret,
VolumeName: secret.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: secret.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
MountAccessMode: mountAccessMode,
})
}
return result, nil
Expand Down Expand Up @@ -131,13 +141,20 @@ func (o KubernetesClient) getAutomountingConfigmaps() ([]AutomountInfo, error) {
}
sort.Strings(keys)
}

mountAccessMode, err := getMountAccessModeFromAnnotation(cm.Annotations)
if err != nil {
return nil, err
}

result = append(result, AutomountInfo{
VolumeType: VolumeTypeConfigmap,
VolumeName: cm.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: cm.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
VolumeType: VolumeTypeConfigmap,
VolumeName: cm.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: cm.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
MountAccessMode: mountAccessMode,
})
}
return result, nil
Expand All @@ -158,3 +175,21 @@ func getMountAsFromAnnotation(annotations map[string]string) MountAs {
return MountAsFile
}
}

func getMountAccessModeFromAnnotation(annotations map[string]string) (*int32, error) {
accessModeStr, found := annotations[annotationMountAccessMode]
if !found {
return nil, nil
}
accessMode64, err := strconv.ParseInt(accessModeStr, 0, 32)
if err != nil {
return nil, err
}

if accessMode64 < 0 || accessMode64 > 0777 {
return nil, fmt.Errorf("invalid access mode annotation: value '%s' parsed to %o (octal). Must be in range 0000-0777", accessModeStr, accessMode64)
}

accessMode32 := int32(accessMode64)
return &accessMode32, nil
}
94 changes: 94 additions & 0 deletions pkg/configAutomount/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/redhat-developer/odo/pkg/kclient"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"
)

func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
Expand Down Expand Up @@ -93,6 +94,24 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
annotationReadOnlyName: "true",
})

secretMountAccessMode := corev1.Secret{}
secretMountAccessMode.SetName("secretMountAccessMode")
secretMountAccessMode.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
secretMountAccessMode.SetAnnotations(map[string]string{
annotationMountAccessMode: "0400",
})

secretMountAccessModeInvalid := corev1.Secret{}
secretMountAccessModeInvalid.SetName("secretMountAccessModeInvalid")
secretMountAccessModeInvalid.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
secretMountAccessModeInvalid.SetAnnotations(map[string]string{
annotationMountAccessMode: "01444",
})

defaultCM1 := corev1.ConfigMap{}
defaultCM1.SetName("defaultCM1")
defaultCM1.SetLabels(map[string]string{
Expand Down Expand Up @@ -145,6 +164,24 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
annotationReadOnlyName: "true",
})

cmMountAccessMode := corev1.ConfigMap{}
cmMountAccessMode.SetName("cmMountAccessMode")
cmMountAccessMode.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
cmMountAccessMode.SetAnnotations(map[string]string{
annotationMountAccessMode: "0444",
})

cmMountAccessModeInvalid := corev1.ConfigMap{}
cmMountAccessModeInvalid.SetName("cmMountAccessModeInvalid")
cmMountAccessModeInvalid.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
cmMountAccessModeInvalid.SetAnnotations(map[string]string{
annotationMountAccessMode: "01444",
})

type fields struct {
kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
}
Expand Down Expand Up @@ -330,6 +367,63 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
},
wantErr: false,
},
{
name: "Secret and ConfigMap with mount-access-mode annotations",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{secretMountAccessMode}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{cmMountAccessMode}, nil).AnyTimes()
return client
},
},
want: []AutomountInfo{
{
VolumeType: VolumeTypeSecret,
VolumeName: "secretMountAccessMode",
MountPath: "/etc/secret/secretMountAccessMode",
MountAs: MountAsFile,
MountAccessMode: pointer.Int32(0400),
},
{
VolumeType: VolumeTypeConfigmap,
VolumeName: "cmMountAccessMode",
MountPath: "/etc/config/cmMountAccessMode",
MountAs: MountAsFile,
MountAccessMode: pointer.Int32(0444),
},
},
wantErr: false,
},
{
name: "Secret with invalid mount-access-mode annotation",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{secretMountAccessModeInvalid}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{}, nil).AnyTimes()
return client
},
},
want: nil,
wantErr: true,
},
{
name: "Configmap with invalid mount-access-mode annotation",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{cmMountAccessModeInvalid}, nil).AnyTimes()
return client
},
},
want: nil,
wantErr: true,
},
{
name: "PVC, Secret and ConfigMap read-only",
fields: fields{
Expand Down
8 changes: 6 additions & 2 deletions pkg/devfile/adapters/kubernetes/storage/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ func mountSecretAsFile(volumeInfo configAutomount.AutomountInfo, containers, ini
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: volumeInfo.VolumeName,
SecretName: volumeInfo.VolumeName,
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down Expand Up @@ -352,7 +353,8 @@ func mountSecretAsSubpath(volumeInfo configAutomount.AutomountInfo, containers,
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: volumeInfo.VolumeName,
SecretName: volumeInfo.VolumeName,
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down Expand Up @@ -386,6 +388,7 @@ func mountConfigMapAsFile(volumeInfo configAutomount.AutomountInfo, containers,
Name: volumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
DefaultMode: volumeInfo.MountAccessMode,
LocalObjectReference: corev1.LocalObjectReference{
Name: volumeInfo.VolumeName,
},
Expand Down Expand Up @@ -429,6 +432,7 @@ func mountConfigMapAsSubpath(volumeInfo configAutomount.AutomountInfo, container
LocalObjectReference: corev1.LocalObjectReference{
Name: volumeInfo.VolumeName,
},
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down
Loading