Skip to content

Commit

Permalink
feat: add encrypted volume support via LUKS (#28)
Browse files Browse the repository at this point in the history
* feat: add encrypted volume support via LUKS

Signed-off-by: Patrik Cyvoct <[email protected]>
  • Loading branch information
Sh4d1 authored Aug 20, 2020
1 parent c07f021 commit 2092531
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 30 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ ARG COMMIT_SHA
ARG BUILD_DATE
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -ldflags "-w -s -X github.com/scaleway/scaleway-csi/driver.driverVersion=${TAG} -X github.com/scaleway/scaleway-csi/driver.buildDate=${BUILD_DATE} -X github.com/scaleway/scaleway-csi/driver.gitCommit=${COMMIT_SHA} " -o scaleway-csi ./cmd/scaleway-csi

FROM alpine:3.11
RUN apk update && apk add --no-cache e2fsprogs e2fsprogs-extra xfsprogs xfsprogs-extra ca-certificates && update-ca-certificates
FROM alpine:3.12
RUN apk update && apk add --no-cache e2fsprogs e2fsprogs-extra xfsprogs xfsprogs-extra cryptsetup ca-certificates && update-ca-certificates
WORKDIR /
COPY --from=builder /go/src/github.com/scaleway/scaleway-csi/scaleway-csi .
ENTRYPOINT ["/scaleway-csi"]
41 changes: 30 additions & 11 deletions driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
scwVolumeZone = DriverName + "/volume-zone"

volumeTypeKey = "type"
encryptedKey = "encrypted"
)

type controllerService struct {
Expand All @@ -58,7 +59,7 @@ func newControllerService(config *DriverConfig) controllerService {
// CreateVolume creates a new volume with the given CreateVolumeRequest.
// This function is idempotent
func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
klog.V(4).Infof("CreateVolume: called with %+v", *req)
klog.V(4).Infof("CreateVolume: called with %s", stripSecretFromReq(*req))

volumeName := req.GetName()
if volumeName == "" {
Expand All @@ -75,11 +76,20 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
return nil, status.Errorf(codes.InvalidArgument, "volumeCapabilities not supported: %s", err)
}

encrypted := false

volumeType := scaleway.DefaultVolumeType
for key, value := range req.GetParameters() {
switch strings.ToLower(key) {
case volumeTypeKey:
volumeType = instance.VolumeVolumeType(value)
case encryptedKey:
encryptedValue, err := strconv.ParseBool(value)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid bool value (%s) for parameter %s: %v", value, key, err)
}
// TODO check if this value has changed?
encrypted = encryptedValue
default:
return nil, status.Errorf(codes.InvalidArgument, "invalid parameter key %s", key)
}
Expand Down Expand Up @@ -114,6 +124,9 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
VolumeId: volume.Zone.String() + "/" + volume.ID,
CapacityBytes: int64(volume.Size),
AccessibleTopology: newAccessibleTopology(volume.Zone),
VolumeContext: map[string]string{
encryptedKey: strconv.FormatBool(encrypted),
},
},
}, nil
}
Expand Down Expand Up @@ -201,6 +214,9 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
Segments: segments,
},
},
VolumeContext: map[string]string{
encryptedKey: strconv.FormatBool(encrypted),
},
},
}, nil
}
Expand Down Expand Up @@ -228,6 +244,9 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
Segments: segments,
},
},
VolumeContext: map[string]string{
encryptedKey: strconv.FormatBool(encrypted),
},
},
}, nil
}
Expand All @@ -239,7 +258,7 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
// DeleteVolume deprovision a volume.
// This operation MUST be idempotent.
func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
klog.V(4).Infof("DeleteVolume called with %+v", *req)
klog.V(4).Infof("DeleteVolume called with %s", stripSecretFromReq(*req))
volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId())
if err != nil {
return nil, err
Expand Down Expand Up @@ -281,7 +300,7 @@ func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVol
// ControllerPublishVolume perform the work that is necessary for making the volume available on the given node.
// This operation MUST be idempotent.
func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
klog.V(4).Infof("ControllerPublishVolume called with %+v", *req)
klog.V(4).Infof("ControllerPublishVolume called with %s", stripSecretFromReq(*req))

volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId())
if err != nil {
Expand Down Expand Up @@ -370,7 +389,7 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs
// ControllerUnpublishVolume is the reverse operation of ControllerPublishVolume
// This operation MUST be idempotent.
func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
klog.V(4).Infof("ControllerUnpublishVolume called with %+v", *req)
klog.V(4).Infof("ControllerUnpublishVolume called with %s", stripSecretFromReq(*req))

volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId())
if err != nil {
Expand Down Expand Up @@ -426,7 +445,7 @@ func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req *
// volume capabilities specified in the request are supported.
//This operation MUST be idempotent.
func (d *controllerService) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
klog.V(4).Infof("ValidateVolumeCapabilities called with %+v", *req)
klog.V(4).Infof("ValidateVolumeCapabilities called with %s", stripSecretFromReq(*req))
volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId())
if err != nil {
return nil, err
Expand Down Expand Up @@ -462,7 +481,7 @@ func (d *controllerService) ValidateVolumeCapabilities(ctx context.Context, req

// ListVolumes returns the list of the requested volumes
func (d *controllerService) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
klog.V(4).Infof("ListVolumes called with %+v", *req)
klog.V(4).Infof("ListVolumes called with %s", stripSecretFromReq(*req))
var numberResults int
var err error

Expand Down Expand Up @@ -526,7 +545,7 @@ func (d *controllerService) GetCapacity(ctx context.Context, req *csi.GetCapacit

// ControllerGetCapabilities returns the supported capabilities of controller service provided by the Plugin.
func (d *controllerService) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
klog.V(4).Infof("ControllerGetCapabilities called with %v", *req)
klog.V(4).Infof("ControllerGetCapabilities called with %v", stripSecretFromReq(*req))
var capabilities []*csi.ControllerServiceCapability
for _, capability := range controllerCapabilities {
capabilities = append(capabilities, &csi.ControllerServiceCapability{
Expand All @@ -542,7 +561,7 @@ func (d *controllerService) ControllerGetCapabilities(ctx context.Context, req *

// CreateSnapshot creates a snapshot of the given volume
func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
klog.V(4).Infof("CreateSnapshot called with %v", *req)
klog.V(4).Infof("CreateSnapshot called with %v", stripSecretFromReq(*req))
sourceVolumeID, sourceVolumeZone, err := getSourceVolumeIDAndZone(req.GetSourceVolumeId())
if err != nil {
return nil, err
Expand Down Expand Up @@ -620,7 +639,7 @@ func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateS

// DeleteSnapshot deletes the given snapshot
func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
klog.V(4).Infof("DeleteSnapshot called with %+v", *req)
klog.V(4).Infof("DeleteSnapshot called with %s", stripSecretFromReq(*req))
snapshotID, snapshotZone, err := getSnapshotIDAndZone(req.GetSnapshotId())
if err != nil {
return nil, err
Expand All @@ -646,7 +665,7 @@ func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteS
// they were created. ListSnapshots SHALL NOT list a snapshot that
// is being created but has not been cut successfully yet.
func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
klog.V(4).Infof("ListSnapshots called with %+v", *req)
klog.V(4).Infof("ListSnapshots called with %s", stripSecretFromReq(*req))
var numberResults int
var err error

Expand Down Expand Up @@ -728,7 +747,7 @@ func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnap

// ControllerExpandVolume expands the given volume
func (d *controllerService) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
klog.V(4).Infof("ControllerExpandVolume called with %+v", *req)
klog.V(4).Infof("ControllerExpandVolume called with %s", stripSecretFromReq(*req))
volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId())
if err != nil {
return nil, err
Expand Down
101 changes: 99 additions & 2 deletions driver/diskutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (
)

const (
diskByIDPath = "/dev/disk/by-id"
diskSCWPrefix = "scsi-0SCW_b_ssd_volume-"
diskByIDPath = "/dev/disk/by-id"
diskSCWPrefix = "scsi-0SCW_b_ssd_volume-"
diskLuksMapperPrefix = "scw-luks-"
diskLuksMapperPath = "/dev/mapper/"

defaultFSType = "ext4"

Expand Down Expand Up @@ -55,6 +57,16 @@ type DiskUtils interface {

// Resize resizes the given volumes
Resize(targetPath string, devicePath string) error

// EncryptAndOpenDevice encrypts the volume with the given ID with the given passphrase and open it
// If the device is already encrypted (LUKS header present), it will only open the device
EncryptAndOpenDevice(volumeID string, passphrase string) (string, error)

// CloseDevice closes the encrypted device with the given ID
CloseDevice(volumeID string) error

// GetMappedDevicePath returns the path on where the encrypted device with the given ID is mapped
GetMappedDevicePath(volumeID string) (string, error)
}

type diskUtils struct{}
Expand All @@ -63,6 +75,91 @@ func newDiskUtils() *diskUtils {
return &diskUtils{}
}

func (d *diskUtils) EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) {
encryptedDevicePath, err := d.GetMappedDevicePath(volumeID)
if err != nil {
return "", err
}

if encryptedDevicePath != "" {
// device is already encrypted and open
return encryptedDevicePath, nil
}

// let's check if the device is aready a luks device
devicePath, err := d.GetDevicePath(volumeID)
if err != nil {
return "", fmt.Errorf("error getting device path for volume %s: %w", volumeID, err)
}
isLuks, err := luksIsLuks(devicePath)
if err != nil {
return "", fmt.Errorf("error checking if device %s is a luks device: %w", devicePath, err)
}

if !isLuks {
// need to format the device
err = luksFormat(devicePath, passphrase)
if err != nil {
return "", fmt.Errorf("error formating device %s: %w", devicePath, err)
}
}

err = luksOpen(devicePath, diskLuksMapperPrefix+volumeID, passphrase)
if err != nil {
return "", fmt.Errorf("error luks opening device %s: %w", devicePath, err)
}
return diskLuksMapperPath + diskLuksMapperPrefix + volumeID, nil
}

func (d *diskUtils) CloseDevice(volumeID string) error {
encryptedDevicePath, err := d.GetMappedDevicePath(volumeID)
if err != nil {
return err
}

if encryptedDevicePath != "" {
err = luksClose(diskLuksMapperPrefix + volumeID)
if err != nil {
return fmt.Errorf("error luks closing %s: %w", encryptedDevicePath, err)
}
}

return nil
}

func (d *diskUtils) GetMappedDevicePath(volumeID string) (string, error) {
mappedPath := diskLuksMapperPath + diskLuksMapperPrefix + volumeID
_, err := os.Stat(mappedPath)
if err != nil {
// if the mapped device does not exists on disk, it's not open
if os.IsNotExist(err) {
return "", nil
}
return "", fmt.Errorf("error checking stat on %s: %w", mappedPath, err)
}

statusStdout, err := luksStatus(diskLuksMapperPrefix + volumeID)
if err != nil {
return "", fmt.Errorf("error checking luks status on %s: %w", diskLuksMapperPrefix+volumeID, err)
}

statusLines := strings.Split(string(statusStdout), "\n")

if len(statusLines) == 0 {
return "", fmt.Errorf("luksStatus stdout have 0 lines")
}

// first line should look like
// /dev/mapper/<name> is active.
if !strings.HasSuffix(statusLines[0], "is active.") {
// when a device is not active, an error exit code is thrown
// something went wrong if we reach here
return "", fmt.Errorf("luksStatus returned ok, but device %s is not active", diskLuksMapperPrefix+volumeID)
}

return mappedPath, nil
}

func (d *diskUtils) FormatAndMount(targetPath string, devicePath string, fsType string, mountOptions []string) error {
if fsType == "" {
fsType = defaultFSType
Expand Down
39 changes: 39 additions & 0 deletions driver/helpers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package driver

import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"

"github.com/container-storage-interface/spec/lib/go/csi"
Expand Down Expand Up @@ -243,3 +245,40 @@ func createMountPoint(path string, file bool) error {
}
return nil
}

var secretsField = "Secrets"

func stripSecretFromReq(req interface{}) string {
ret := "{"

reqValue := reflect.ValueOf(req)
reqType := reqValue.Type()
if reqType.Kind() == reflect.Struct {
for i := 0; i < reqValue.NumField(); i++ {
field := reqType.Field(i)
value := reqValue.Field(i)

valueToPrint := fmt.Sprintf("%+v", value.Interface())

if field.Name == secretsField && value.Kind() == reflect.Map {
valueToPrint = "["
for j := 0; j < len(value.MapKeys()); j++ {
valueToPrint += fmt.Sprintf("%s:<redacted>", value.MapKeys()[j].String())
if j != len(value.MapKeys())-1 {
valueToPrint += " "
}
}
valueToPrint += "]"
}

ret += fmt.Sprintf("%s:%s", field.Name, valueToPrint)
if i != reqValue.NumField()-1 {
ret += " "
}
}
}

ret += "}"

return ret
}
Loading

0 comments on commit 2092531

Please sign in to comment.