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

[feat] Enable support for encrypted volumes for CSI driver #299

Merged
merged 45 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
eceefc8
- updated templates to support volume encryption
prajwalvathreya Oct 29, 2024
071a28b
- updated main.go to pickup environment variable for setting `volumeE…
prajwalvathreya Oct 29, 2024
f5bbd7f
- updated driver.go to pass environment variable `volumeEncryption` w…
prajwalvathreya Oct 29, 2024
739f91a
- updated controllerserver.go to use variable `volumeEncryption` when…
prajwalvathreya Oct 29, 2024
aa31499
Updated controllerserver_helper.go for the following:
prajwalvathreya Oct 29, 2024
1ac170a
- updated linode_client.go to have ListRegion function as part of the…
prajwalvathreya Oct 29, 2024
a6f2c6d
- fixed lint errors
prajwalvathreya Oct 30, 2024
57d7288
- mock files generated after running `make generate-mock`
prajwalvathreya Oct 30, 2024
15110b6
- added comments to driver_test.go
prajwalvathreya Oct 30, 2024
c6e8212
- updated helm charts to allow encryption
prajwalvathreya Oct 31, 2024
09ca3af
Merge branch 'main' into volume-encryption
prajwalvathreya Oct 31, 2024
a6c8358
- fixed CI errors in controllerserver_helper_test.go
prajwalvathreya Nov 1, 2024
11a596c
Merge remote-tracking branch 'origin/volume-encryption' into volume-e…
prajwalvathreya Nov 1, 2024
0ab93ad
- removed environment variable VOLUME_ENCRYPTION
prajwalvathreya Nov 4, 2024
2b4973a
- updated URL target in controllerserver_helper.go
prajwalvathreya Nov 4, 2024
9b74ea8
- added log to check encryption status
prajwalvathreya Nov 4, 2024
58dc736
- added log to check encryption status
prajwalvathreya Nov 4, 2024
7e01533
- added log to check encryption status
prajwalvathreya Nov 4, 2024
da1e8f1
- added log to check encryption status
prajwalvathreya Nov 4, 2024
5fd1358
- removed logging statements used for testing
prajwalvathreya Nov 4, 2024
20ebc2a
Updated comment in values.yaml on the volumeEncryption variable
prajwalvathreya Nov 4, 2024
3bcf4ae
- added unit tests for testing encryption related functions
prajwalvathreya Nov 5, 2024
ecfd939
- refactored ListRegions to GetRegion
prajwalvathreya Nov 5, 2024
3152804
- changed templated to not create `parameters` map by default
prajwalvathreya Nov 6, 2024
ccf359a
- refactored function to just take `req.GetParameters()` and handle k…
prajwalvathreya Nov 6, 2024
d6b935b
- fixed lint error in values.yaml
prajwalvathreya Nov 6, 2024
3592d21
- updated `createAndWaitForVolume` call to take in map
prajwalvathreya Nov 7, 2024
0569170
- added additional test cases for prepareparams and createvolumecontext
prajwalvathreya Nov 7, 2024
681e2d0
- reverted helm template files for default storage options
prajwalvathreya Nov 7, 2024
01023b5
- updated documentation on how to use encryption
prajwalvathreya Nov 7, 2024
f11c229
- added new lines at the end of template files
prajwalvathreya Nov 8, 2024
2b3e4df
- removed encryption variable comments
prajwalvathreya Nov 8, 2024
2935055
- returned `err`
prajwalvathreya Nov 8, 2024
5f1c306
- removed annotations
prajwalvathreya Nov 8, 2024
345e7f1
- updated documentation with table of contents
prajwalvathreya Nov 8, 2024
2beb183
- removed volumeEncryption from createVolumeContext
prajwalvathreya Nov 8, 2024
c24ce38
- removed volumeEncryption from createVolumeContext and updated test …
prajwalvathreya Nov 11, 2024
e0c16b1
- added example for encrypted blockstorage volume
prajwalvathreya Nov 12, 2024
c38e0e8
- updated pod name in example
prajwalvathreya Nov 12, 2024
b1caf89
- added new line to example yaml
prajwalvathreya Nov 12, 2024
10e2e3d
- optimized prepareparams for volume request to handle region
prajwalvathreya Nov 13, 2024
8f67953
Merge branch 'main' into volume-encryption
prajwalvathreya Nov 13, 2024
58819f1
- updated logs and fixed lint errors
prajwalvathreya Nov 13, 2024
6e530f5
- added return to fix linting issue in safe_mounter_test.go
prajwalvathreya Nov 13, 2024
02a5bd1
- fixing lint errors in controllerserver_test.go
prajwalvathreya Nov 13, 2024
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 @@ -7,10 +7,13 @@ metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
{{- end }}
{{- if .Values.volumeTags }}
parameters:
{{- if .Values.volumeTags }}
linodebs.csi.linode.com/volumeTags: {{ join "," .Values.volumeTags }}
{{- end}}
{{- if .Values.volumeEncryption }}
linodebs.csi.linode.com/encrypted: "true"
{{- end}}
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved
allowVolumeExpansion: true
provisioner: linodebs.csi.linode.com
reclaimPolicy: Retain
5 changes: 4 additions & 1 deletion helm-chart/csi-driver/templates/linode-block-storage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
{{- end }}
{{- if .Values.volumeTags }}
parameters:
{{- if .Values.volumeTags }}
linodebs.csi.linode.com/volumeTags: {{ join "," .Values.volumeTags }}
{{- end}}
{{- if .Values.volumeEncryption }}
linodebs.csi.linode.com/encrypted: "true"
{{- end}}
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved
allowVolumeExpansion: true
provisioner: linodebs.csi.linode.com
3 changes: 3 additions & 0 deletions helm-chart/csi-driver/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ enableMetrics: false
# default metrics address port
metricsPort: 8081

# enable encryption for volumes by default
volumeEncryption: false

prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved
# (OPTIONAL) Label prefix for the Linode Block Storage volumes created by this driver.
volumeLabelPrefix: ""

Expand Down
2 changes: 1 addition & 1 deletion internal/driver/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
}

// Create the volume
vol, err := cs.createAndWaitForVolume(ctx, volName, sizeGB, req.GetParameters()[VolumeTags], sourceVolInfo, accessibilityRequirements)
vol, err := cs.createAndWaitForVolume(ctx, volName, req.GetParameters()[VolumeTags], req.GetParameters()[VolumeEncryption], sizeGB, sourceVolInfo, accessibilityRequirements)
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
metrics.RecordMetrics(metrics.ControllerCreateVolumeTotal, metrics.ControllerCreateVolumeDuration, metrics.Failed, functionStartTime)
return &csi.CreateVolumeResponse{}, err
Expand Down
90 changes: 81 additions & 9 deletions internal/driver/controllerserver_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ const (
// devicePathKey is the key used in the publish context map when a volume is
// published/attached to an instance.
devicePathKey = "devicePath"

// volumeEncryption is the key used in the context map for encryption
VolumeEncryption = Name + "/encrypted"
)

// canAttach indicates whether or not another volume can be attached to the
Expand Down Expand Up @@ -186,7 +189,7 @@ func (cs *ControllerServer) getContentSourceVolume(ctx context.Context, contentS
// attemptCreateLinodeVolume creates a Linode volume while ensuring idempotency.
// It checks for existing volumes with the same label and either returns the existing
// volume or creates a new one, optionally cloning from a source volume.
func (cs *ControllerServer) attemptCreateLinodeVolume(ctx context.Context, label string, sizeGB int, tags string, sourceVolume *linodevolumes.LinodeVolumeKey, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
func (cs *ControllerServer) attemptCreateLinodeVolume(ctx context.Context, label, tags, volumeEncryption string, sizeGB int, sourceVolume *linodevolumes.LinodeVolumeKey, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Attempting to create Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags)
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -216,7 +219,7 @@ func (cs *ControllerServer) attemptCreateLinodeVolume(ctx context.Context, label
return cs.cloneLinodeVolume(ctx, label, sourceVolume.VolumeID)
}

return cs.createLinodeVolume(ctx, label, sizeGB, tags, accessibilityRequirements)
return cs.createLinodeVolume(ctx, label, tags, volumeEncryption, sizeGB, accessibilityRequirements)
}

// Helper function to extract region from topology
Expand All @@ -237,7 +240,7 @@ func getRegionFromTopology(requirements *csi.TopologyRequirement) string {

// createLinodeVolume creates a new Linode volume with the specified label, size, and tags.
// It returns the created volume or an error if the creation fails.
func (cs *ControllerServer) createLinodeVolume(ctx context.Context, label string, sizeGB int, tags string, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
func (cs *ControllerServer) createLinodeVolume(ctx context.Context, label, tags, volumeEncryption string, sizeGB int, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Creating Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags)
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -250,11 +253,27 @@ func (cs *ControllerServer) createLinodeVolume(ctx context.Context, label string
}
}

// Check encryption status and if it's supported in the specified region.
var encryptionStatus string
if volumeEncryption == True {
supported, err := cs.isEncryptionSupported(ctx, region)
if err != nil {
return nil, err
}
if !supported {
return nil, errInternal("Volume encryption is not supported in the %s region", region)
}
encryptionStatus = "enabled"
} else {
encryptionStatus = "disabled"
}
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved

// Prepare the volume creation request with region, label, and size.
volumeReq := linodego.VolumeCreateOptions{
Region: region,
Label: label,
Size: sizeGB,
Region: region,
Label: label,
Size: sizeGB,
Encryption: encryptionStatus,
}

// If tags are provided, split them into a slice for the request.
Expand All @@ -272,6 +291,36 @@ func (cs *ControllerServer) createLinodeVolume(ctx context.Context, label string
return result, nil
}

// isEncryptionSupported is a helper function that checks if the specified region supports volume encryption.
// It returns true or false based on the support for encryption in that region.
func (cs *ControllerServer) isEncryptionSupported(ctx context.Context, region string) (bool, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Checking if encryption is supported for region", "region", region)

// Fetch the list of regions from the Linode API
regions, err := cs.client.ListRegions(ctx, nil)
if err != nil {
return false, errInternal("failed to fetch regions: %v", err)
}

// Look for the specified region and check if encryption is supported.
for i := range regions {
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved
r := &regions[i]
if r.ID == region {
for _, capability := range r.Capabilities {
if capability == "Block Storage Encryption" { // https://techdocs.akamai.com/linode-api/reference/get-regions
return true, nil
}
}
break
}
}

// If the region was found but does not support encryption, return false
log.V(4).Info("Encryption not supported in the specified region", "region", region)
return false, nil
}

// cloneLinodeVolume clones a Linode volume using the specified source ID and label.
// It returns the cloned volume or an error if the cloning fails.
func (cs *ControllerServer) cloneLinodeVolume(ctx context.Context, label string, sourceID int) (*linodego.Volume, error) {
Expand Down Expand Up @@ -448,12 +497,12 @@ func (cs *ControllerServer) createVolumeContext(ctx context.Context, req *csi.Cr

// createAndWaitForVolume attempts to create a new volume and waits for it to become active.
// It logs the process and handles any errors that occur during creation or waiting.
func (cs *ControllerServer) createAndWaitForVolume(ctx context.Context, name string, sizeGB int, tags string, sourceInfo *linodevolumes.LinodeVolumeKey, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
func (cs *ControllerServer) createAndWaitForVolume(ctx context.Context, name, tags, volumeEncryption string, sizeGB int, sourceInfo *linodevolumes.LinodeVolumeKey, accessibilityRequirements *csi.TopologyRequirement) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Entering createAndWaitForVolume()", "name", name, "sizeGB", sizeGB, "tags", tags)
defer log.V(4).Info("Exiting createAndWaitForVolume()")

vol, err := cs.attemptCreateLinodeVolume(ctx, name, sizeGB, tags, sourceInfo, accessibilityRequirements)
vol, err := cs.attemptCreateLinodeVolume(ctx, name, tags, volumeEncryption, sizeGB, sourceInfo, accessibilityRequirements)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -649,8 +698,31 @@ func (cs *ControllerServer) attachVolume(ctx context.Context, volumeID, linodeID
log.V(4).Info("Entering attachVolume()", "volume_id", volumeID, "node_id", linodeID)
defer log.V(4).Info("Exiting attachVolume()")

// Get the details of the volume to check for attachment possibilities
volume, err := cs.client.GetVolume(ctx, volumeID)
if err != nil {
return status.Errorf(codes.Internal, "failed to retrieve volume details: %v", err)
}

// Check if the volume is encrypted
isEncrypted := volume.Encryption == "enabled"
var linode *linodego.Instance

// If the volume is encrypted, get the details of the Linode instance and check region compatibility
if isEncrypted {
linode, err = cs.client.GetInstance(ctx, linodeID)
if err != nil {
return status.Errorf(codes.Internal, "failed to retrieve Linode instance details: %v", err)
}

// Ensure the encrypted volume and Linode are in the same region
if volume.Region != linode.Region {
return status.Errorf(codes.FailedPrecondition, "encrypted volume and Linode instance must be in the same region for attachment")
}
}
prajwalvathreya marked this conversation as resolved.
Show resolved Hide resolved

persist := false
_, err := cs.client.AttachVolume(ctx, volumeID, &linodego.VolumeAttachOptions{
_, err = cs.client.AttachVolume(ctx, volumeID, &linodego.VolumeAttachOptions{
LinodeID: linodeID,
PersistAcrossBoots: &persist,
})
Expand Down
22 changes: 13 additions & 9 deletions internal/driver/controllerserver_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,15 @@ func TestCreateAndWaitForVolume(t *testing.T) {
}

testCases := []struct {
name string
volumeName string
sizeGB int
tags string
sourceInfo *linodevolumes.LinodeVolumeKey
setupMocks func()
expectedVolume *linodego.Volume
expectedError error
name string
volumeName string
sizeGB int
tags string
volumeEncryption string
sourceInfo *linodevolumes.LinodeVolumeKey
setupMocks func()
expectedVolume *linodego.Volume
expectedError error
}{
{
name: "Successful volume creation",
Expand Down Expand Up @@ -321,7 +322,7 @@ func TestCreateAndWaitForVolume(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
tc.setupMocks()

volume, err := cs.createAndWaitForVolume(context.Background(), tc.volumeName, tc.sizeGB, tc.tags, tc.sourceInfo, topology)
volume, err := cs.createAndWaitForVolume(context.Background(), tc.volumeName, tc.tags, tc.volumeEncryption, tc.sizeGB, tc.sourceInfo, topology)

if err != nil && !reflect.DeepEqual(tc.expectedError, err) {
if tc.expectedError != nil {
Expand Down Expand Up @@ -1006,6 +1007,7 @@ func TestAttachVolume(t *testing.T) {
volumeID: 123,
linodeID: 456,
setupMocks: func() {
mockClient.EXPECT().GetVolume(gomock.Any(), 123).Return(&linodego.Volume{}, nil)
mockClient.EXPECT().AttachVolume(gomock.Any(), 123, gomock.Any()).Return(&linodego.Volume{}, nil)
},
expectedError: nil,
Expand All @@ -1015,6 +1017,7 @@ func TestAttachVolume(t *testing.T) {
volumeID: 789,
linodeID: 101,
setupMocks: func() {
mockClient.EXPECT().GetVolume(gomock.Any(), 789).Return(&linodego.Volume{}, nil)
mockClient.EXPECT().AttachVolume(gomock.Any(), 789, gomock.Any()).Return(nil, &linodego.Error{Message: "Volume 789 is already attached"})
},
expectedError: status.Error(codes.Unavailable, "attach volume: [000] Volume 789 is already attached"),
Expand All @@ -1024,6 +1027,7 @@ func TestAttachVolume(t *testing.T) {
volumeID: 202,
linodeID: 303,
setupMocks: func() {
mockClient.EXPECT().GetVolume(gomock.Any(), 202).Return(&linodego.Volume{}, nil)
mockClient.EXPECT().AttachVolume(gomock.Any(), 202, gomock.Any()).Return(nil, errors.New("API error"))
},
expectedError: status.Error(codes.Internal, "attach volume: API error"),
Expand Down
6 changes: 5 additions & 1 deletion internal/driver/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@ func TestControllerPublishVolume(t *testing.T) {
},
expectLinodeClientCalls: func(m *mocks.MockLinodeClient) {
m.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(&linodego.Instance{ID: 1003, Specs: &linodego.InstanceSpec{Memory: 16 << 10}}, nil)
m.EXPECT().GetVolume(gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil).AnyTimes()
m.EXPECT().WaitForVolumeLinodeID(gomock.Any(), 630706045, gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil)
m.EXPECT().AttachVolume(gomock.Any(), 630706045, gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil)
m.EXPECT().ListInstanceVolumes(gomock.Any(), 1003, gomock.Any()).Return([]linodego.Volume{{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}}, nil)
m.EXPECT().ListInstanceDisks(gomock.Any(), 1003, gomock.Any()).Return([]linodego.InstanceDisk{}, nil)
m.EXPECT().GetVolume(gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil)
},
expectedError: nil,
},
Expand Down Expand Up @@ -623,6 +623,10 @@ func (c *fakeLinodeClient) ListInstanceDisks(_ context.Context, _ int, _ *linode
return c.disks, nil
}

func (flc *fakeLinodeClient) ListRegions(_ context.Context, _ *linodego.ListOptions) ([]linodego.Region, error) {
return []linodego.Region{}, nil
}

//nolint:nilnil // TODO: re-work tests
func (flc *fakeLinodeClient) GetInstance(context.Context, int) (*linodego.Instance, error) {
return nil, nil
Expand Down
1 change: 1 addition & 0 deletions internal/driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestDriverSuite(t *testing.T) {
Memory: 4 << 30, // 4GiB
}
linodeDriver := GetLinodeDriver(context.Background())
// variables that are picked up from the environment
enableMetrics := "true"
metricsPort := "10251"
if err := linodeDriver.SetupLinodeDriver(context.Background(), fakeCloudProvider, mounter, deviceUtils, md, driver, vendorVersion, bsPrefix, encrypt, enableMetrics, metricsPort); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions mocks/mock_cryptsetupclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mocks/mock_device.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions mocks/mock_filesystem.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions mocks/mock_linodeclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mocks/mock_metadata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions mocks/mock_safe-mounter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/linode-client/linode_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type LinodeClient interface {
ListVolumes(context.Context, *linodego.ListOptions) ([]linodego.Volume, error)
ListInstanceVolumes(ctx context.Context, instanceID int, options *linodego.ListOptions) ([]linodego.Volume, error)
ListInstanceDisks(ctx context.Context, instanceID int, options *linodego.ListOptions) ([]linodego.InstanceDisk, error)
ListRegions(context.Context, *linodego.ListOptions) ([]linodego.Region, error)

GetInstance(context.Context, int) (*linodego.Instance, error)
GetVolume(context.Context, int) (*linodego.Volume, error)
Expand Down
Loading