Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
89 changes: 86 additions & 3 deletions docs/encrypted-drives.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,86 @@
## 🔒 Encrypted Drives using LUKS
## 📜 Table of Contents

1. [🔒 Encrypted Block Storage](#encrypted-block-storage)
- [Example StorageClass](#example-storageclass-with-blockstorage)
- [Example PVC](#example-pvc-for-blockstorage)
2. [🔒 Encrypted Drives using LUKS](#encrypted-drives-using-luks)
- [Example StorageClass with LUKS](#example-storageclass-with-luks)
- [Example PVC with LUKS](#example-pvc-with-luks)

**NOTE**: LUKS encryption allows users to bring their own keys and manage them, while BlockStorage encryption is managed by Linode and it's automatically handled on the backend.

### Encrypted Block Storage

**Notes**:

1. **Setting Up Encryption**: In the provided StorageClasses, encryption is activated by specifying `linodebs.csi.linode.com/encrypted: "true"` in the `parameters` field. This signals the CSI driver to provision volumes with encryption enabled, provided encryption is supported in the specified region.
2. **Retention and Expansion Options**:
- The `linode-block-storage-encrypted` StorageClass uses the default `Delete` reclaim policy, meaning that volumes created with this StorageClass will be deleted when the associated PVC is deleted.
- In contrast, the `linode-block-storage-retain-encrypted` StorageClass uses the `Retain` policy. This allows the volume to persist even after the PVC is deleted, ensuring data is preserved until manually removed.
- Both StorageClasses support volume expansion through the `allowVolumeExpansion: true` setting, allowing users to resize volumes as needed without data loss.

3. **Default StorageClass Annotation**: By marking both StorageClasses with `storageclass.kubernetes.io/is-default-class: "true"`, they’re eligible to act as default classes. However, Kubernetes will only treat one StorageClass as the actual default. Consider applying this annotation only to the preferred default StorageClass.
4. **Region Compatibility**: Ensure that encryption is supported in the Linode region where the volumes will be created. If encryption is not available in a specific region, the CSI driver will return an error.
- To check if the region has encryption capability visit https://techdocs.akamai.com/linode-api/reference/get-regions
- For your specific region, check the `capabilities` and see if `Block Storage Encryption` is listed in it.
5. **Usage in PersistentVolumeClaims (PVCs)**: Use the `storageClassName` field in a PVC to reference the desired StorageClass (`linode-block-storage-encrypted` or `linode-block-storage-retain-encrypted`). Each PVC will inherit the encryption settings defined in the referenced StorageClass.

#### Example StorageClass with BlockStorage

```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: linode-block-storage-encrypted
namespace: kube-system
parameters:
linodebs.csi.linode.com/encrypted: "true"
allowVolumeExpansion: true
provisioner: linodebs.csi.linode.com
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: linode-block-storage-retain-encrypted
namespace: kube-system
parameters:
linodebs.csi.linode.com/encrypted: "true"
allowVolumeExpansion: true
provisioner: linodebs.csi.linode.com
reclaimPolicy: Retain
```

#### Example PVC for BlockStorage

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-example-pvc-encrypted
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: linode-block-storage-encrypted
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-example-pvc-encrypted-retain
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: linode-block-storage-retain-encrypted
```

---

### Encrypted Drives using LUKS

**Notes:**

Expand All @@ -12,7 +94,7 @@
The CSI driver is careful to otherwise keep the secret on an ephemeral tmpfs
mount and otherwise refuses to continue.

#### 🔑 Example StorageClass
#### Example StorageClass with LUKS

> [!TIP]
> To use an encryption key per PVC you can make a new StorageClass/Secret
Expand Down Expand Up @@ -45,7 +127,7 @@ stringData:
luksKey: "SECRETGOESHERE"
```

#### 📝 Example PVC
#### Example PVC with LUKS

```yaml
apiVersion: v1
Expand All @@ -72,3 +154,4 @@ spec:
storage: 10Gi
storageClassName: linode-block-storage-retain-luks
```
---
1 change: 0 additions & 1 deletion helm-chart/csi-driver/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ enableMetrics: false

# default metrics address port
metricsPort: 8081

# (OPTIONAL) Label prefix for the Linode Block Storage volumes created by this driver.
volumeLabelPrefix: ""

Expand Down
6 changes: 3 additions & 3 deletions internal/driver/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol

// Prepare the volume parameters such as name and SizeGB from the request.
// This step may involve calculations or adjustments based on the request's content.
volName, sizeGB, size, err := cs.prepareVolumeParams(ctx, req)
params, err := cs.prepareVolumeParams(ctx, req)
if err != nil {
metrics.RecordMetrics(metrics.ControllerCreateVolumeTotal, metrics.ControllerCreateVolumeDuration, metrics.Failed, functionStartTime)
return &csi.CreateVolumeResponse{}, err
Expand All @@ -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, params.VolumeName, req.GetParameters(), params.EncryptionStatus, params.TargetSizeGB, sourceVolInfo, params.Region)
if err != nil {
metrics.RecordMetrics(metrics.ControllerCreateVolumeTotal, metrics.ControllerCreateVolumeDuration, metrics.Failed, functionStartTime)
return &csi.CreateVolumeResponse{}, err
Expand All @@ -103,7 +103,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
volContext := cs.createVolumeContext(ctx, req, vol)

// Prepare and return response
resp := cs.prepareCreateVolumeResponse(ctx, vol, size, volContext, sourceVolInfo, contentSource)
resp := cs.prepareCreateVolumeResponse(ctx, vol, params.Size, volContext, sourceVolInfo, contentSource)

// Record function completion
metrics.RecordMetrics(metrics.ControllerCreateVolumeTotal, metrics.ControllerCreateVolumeDuration, metrics.Completed, functionStartTime)
Expand Down
120 changes: 92 additions & 28 deletions internal/driver/controllerserver_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,21 @@ 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"
)

// Struct to return volume parameters when prepareVolumeParams is called

type VolumeParams struct {
VolumeName string
TargetSizeGB int
Size int64
EncryptionStatus string
Region string
}

// canAttach indicates whether or not another volume can be attached to the
// Linode with the given ID.
//
Expand Down Expand Up @@ -186,9 +199,9 @@ 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, region string) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Attempting to create Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags)
log.V(4).Info("Attempting to create Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags, "encryptionStatus", volumeEncryption, "region", region)

// List existing volumes with the specified label
jsonFilter, err := json.Marshal(map[string]string{"label": label})
Expand Down Expand Up @@ -216,7 +229,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, region)
}

// Helper function to extract region from topology
Expand All @@ -237,24 +250,16 @@ 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, encryptionStatus string, sizeGB int, region string) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Creating Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags)

// Get the region from req.AccessibilityRequirements if it exists. Fall back to the controller's metadata region if not specified.
region := cs.metadata.Region
if accessibilityRequirements != nil {
if topologyRegion := getRegionFromTopology(accessibilityRequirements); topologyRegion != "" {
log.V(4).Info("Using region from topology", "region", topologyRegion)
region = topologyRegion
}
}
log.V(4).Info("Creating Linode volume", "label", label, "sizeGB", sizeGB, "tags", tags, "encryptionStatus", encryptionStatus, "region", region)

// 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 +277,30 @@ 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)

// Get the specifications of specified region from Linode API
regionDetails, err := cs.client.GetRegion(ctx, region)
if err != nil {
return false, errInternal("failed to fetch region %s: %v", region, err)
}

// Check if encryption is supported in the specified region
for _, capability := range regionDetails.Capabilities {
if capability == "Block Storage Encryption" {
return true, nil
}
}

// 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 @@ -403,25 +432,60 @@ func (cs *ControllerServer) validateCreateVolumeRequest(ctx context.Context, req
// prepareVolumeParams prepares the volume parameters for creation.
// It extracts the capacity range from the request, calculates the size,
// and generates a normalized volume name. Returns the volume name and size in GB.
func (cs *ControllerServer) prepareVolumeParams(ctx context.Context, req *csi.CreateVolumeRequest) (volumeName string, targetSizeGB int, size int64, err error) {
func (cs *ControllerServer) prepareVolumeParams(ctx context.Context, req *csi.CreateVolumeRequest) (*VolumeParams, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Entering prepareVolumeParams()", "req", req)
defer log.V(4).Info("Exiting prepareVolumeParams()")

// By default, encryption is disabled
encryptionStatus := "disabled"
// Retrieve the capacity range from the request to determine the size limits for the volume.
capRange := req.GetCapacityRange()
// Get the requested size in bytes, handling any potential errors.
size, err = getRequestCapacitySize(capRange)
size, err := getRequestCapacitySize(capRange)
if err != nil {
return "", 0, 0, err
return nil, err
}

// Get the region from req.AccessibilityRequirements if it exists. Fall back to the controller's metadata region if not specified.
accessibilityRequirements := req.GetAccessibilityRequirements()
region := cs.metadata.Region
if accessibilityRequirements != nil {
if topologyRegion := getRegionFromTopology(accessibilityRequirements); topologyRegion != "" {
log.V(4).Info("Using region from topology", "region", topologyRegion)
region = topologyRegion
}
}

preKey := linodevolumes.CreateLinodeVolumeKey(0, req.GetName())
volumeName = preKey.GetNormalizedLabelWithPrefix(cs.driver.volumeLabelPrefix)
targetSizeGB = bytesToGB(size)
volumeName := preKey.GetNormalizedLabelWithPrefix(cs.driver.volumeLabelPrefix)
targetSizeGB := bytesToGB(size)

// Check if encryption should be enabled
if req.GetParameters()[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"
}

log.V(4).Info("Volume parameters prepared", "volumeName", volumeName, "targetSizeGB", targetSizeGB)
return volumeName, targetSizeGB, size, nil
log.V(4).Info("Volume parameters prepared", "parameters", &VolumeParams{
VolumeName: volumeName,
TargetSizeGB: targetSizeGB,
Size: size,
EncryptionStatus: encryptionStatus,
Region: region,
})
return &VolumeParams{
VolumeName: volumeName,
TargetSizeGB: targetSizeGB,
Size: size,
EncryptionStatus: encryptionStatus,
Region: region,
}, nil
}

// createVolumeContext creates a context map for the volume based on the request parameters.
Expand All @@ -448,12 +512,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 string, parameters map[string]string, encryptionStatus string, sizeGB int, sourceInfo *linodevolumes.LinodeVolumeKey, region string) (*linodego.Volume, error) {
log := logger.GetLogger(ctx)
log.V(4).Info("Entering createAndWaitForVolume()", "name", name, "sizeGB", sizeGB, "tags", tags)
log.V(4).Info("Entering createAndWaitForVolume()", "name", name, "sizeGB", sizeGB, "tags", parameters[VolumeTags], "encryptionStatus", encryptionStatus, "region", region)
defer log.V(4).Info("Exiting createAndWaitForVolume()")

vol, err := cs.attemptCreateLinodeVolume(ctx, name, sizeGB, tags, sourceInfo, accessibilityRequirements)
vol, err := cs.attemptCreateLinodeVolume(ctx, name, parameters[VolumeTags], encryptionStatus, sizeGB, sourceInfo, region)
if err != nil {
return nil, err
}
Expand Down
Loading