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

WIP Add LUKS encryption #337

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,20 @@ $ kubectl exec -ti my-csi-app /bin/sh
/ # ls /data
hello-world
```
## Volume parameters

This plugin supports the following `StorageClass` parameters:

For LUKS encryption:

* `dobs.csi.digitalocean.com/luks-encrypted`: set to the string `"true"` if the volume should be encrypted
with LUKS
* `dobs.csi.digitalocean.com/luks-cipher`: cipher to use; must be supported by the kernel and luks
* `dobs.csi.digitalocean.com/luks-key-size`: key-size to use

For LUKS encrypted volumes, a secret that contains the LUKS key needs to be referenced through
the `csi.storage.k8s.io/node-stage-secret-name` and `csi.storage.k8s.io/node-stage-secret-namespace`
parameter. See the included `StorageClass` definition.

## Upgrading

Expand Down
1 change: 1 addition & 0 deletions cmd/do-csi-plugin/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ FROM alpine:3.15
# e2fsprogs-extra is required for resize2fs used for the resize operation
# blkid: block device identification tool from util-linux
RUN apk add --no-cache ca-certificates \
cryptsetup \
e2fsprogs \
findmnt \
xfsprogs \
Expand Down
54 changes: 33 additions & 21 deletions deploy/kubernetes/releases/csi-digitalocean-v4.0.0/driver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ deletionPolicy: Delete
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: do-block-storage
name: do-block-storage-luks
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: dobs.csi.digitalocean.com
allowVolumeExpansion: true
parameters:
dobs.csi.digitalocean.com/luks-encrypted: "true"
dobs.csi.digitalocean.com/luks-cipher: "aes-xts-plain64"
dobs.csi.digitalocean.com/luks-key-size: "512"
csi.storage.k8s.io/node-stage-secret-namespace: ${pvc.namespace}
csi.storage.k8s.io/node-stage-secret-name: ${pvc.name}-luks-key

---

Expand All @@ -57,24 +63,24 @@ allowVolumeExpansion: true
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: csi-do-controller
name: csi-do-controller-luks
namespace: kube-system
spec:
serviceName: "csi-do"
selector:
matchLabels:
app: csi-do-controller
app: csi-do-controller-luks
replicas: 1
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: csi-do-plugin
labels:
app: csi-do-controller
app: csi-do-controller-luks
role: csi-do
spec:
priorityClassName: system-cluster-critical
serviceAccount: csi-do-controller-sa
serviceAccount: csi-do-controller-sa-luks
containers:
- name: csi-provisioner
image: k8s.gcr.io/sig-storage/csi-provisioner:v3.0.0
Expand Down Expand Up @@ -129,7 +135,7 @@ spec:
- name: socket-dir
mountPath: /var/lib/csi/sockets/pluginproxy/
- name: csi-do-plugin
image: digitalocean/do-csi-plugin:v4.0.0
image: edeckers/do-csi-plugin:v4.0.0-luks
args :
- "--endpoint=$(CSI_ENDPOINT)"
- "--token=$(DIGITALOCEAN_ACCESS_TOKEN)"
Expand Down Expand Up @@ -157,7 +163,7 @@ spec:
kind: ServiceAccount
apiVersion: v1
metadata:
name: csi-do-controller-sa
name: csi-do-controller-sa-luks
namespace: kube-system

---
Expand Down Expand Up @@ -202,7 +208,7 @@ metadata:
name: csi-do-provisioner-binding
subjects:
- kind: ServiceAccount
name: csi-do-controller-sa
name: csi-do-controller-sa-luks
namespace: kube-system
roleRef:
kind: ClusterRole
Expand Down Expand Up @@ -239,7 +245,7 @@ metadata:
name: csi-do-attacher-binding
subjects:
- kind: ServiceAccount
name: csi-do-controller-sa
name: csi-do-controller-sa-luks
namespace: kube-system
roleRef:
kind: ClusterRole
Expand Down Expand Up @@ -275,7 +281,7 @@ metadata:
name: csi-do-snapshotter-binding
subjects:
- kind: ServiceAccount
name: csi-do-controller-sa
name: csi-do-controller-sa-luks
namespace: kube-system
roleRef:
kind: ClusterRole
Expand Down Expand Up @@ -311,7 +317,7 @@ metadata:
name: csi-do-resizer-binding
subjects:
- kind: ServiceAccount
name: csi-do-controller-sa
name: csi-do-controller-sa-luks
namespace: kube-system
roleRef:
kind: ClusterRole
Expand All @@ -329,22 +335,22 @@ roleRef:
kind: DaemonSet
apiVersion: apps/v1
metadata:
name: csi-do-node
name: csi-do-node-luks
namespace: kube-system
spec:
selector:
matchLabels:
app: csi-do-node
app: csi-do-node-luks
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: csi-do-plugin
labels:
app: csi-do-node
app: csi-do-node-luks
role: csi-do
spec:
priorityClassName: system-node-critical
serviceAccount: csi-do-node-sa
serviceAccount: csi-do-node-luks
hostNetwork: true
initContainers:
# Delete automount udev rule running on all DO droplets. The rule mounts
Expand Down Expand Up @@ -385,7 +391,7 @@ spec:
- name: registration-dir
mountPath: /registration/
- name: csi-do-plugin
image: digitalocean/do-csi-plugin:v4.0.0
image: edeckers/do-csi-plugin:v4.0.0-luks
args :
- "--endpoint=$(CSI_ENDPOINT)"
- "--url=$(DIGITALOCEAN_API_URL)"
Expand All @@ -410,6 +416,8 @@ spec:
mountPropagation: "Bidirectional"
- name: device-dir
mountPath: /dev
- name: tmpfs
mountPath: /tmp
volumes:
- name: registration-dir
hostPath:
Expand All @@ -429,20 +437,24 @@ spec:
- name: udev-rules-dir
hostPath:
path: /etc/udev/rules.d/
# to make sure temporary stored luks keys never touch a disk
- name: tmpfs
emptyDir:
medium: Memory
---

apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-do-node-sa
name: csi-do-node-sa-luks
namespace: kube-system

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-do-node-driver-registrar-role
name: csi-do-node-luks-driver-registrar-role
namespace: kube-system
rules:
- apiGroups: [""]
Expand All @@ -454,12 +466,12 @@ rules:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-do-node-driver-registrar-binding
name: csi-do-node-luks-driver-registrar-binding
subjects:
- kind: ServiceAccount
name: csi-do-node-sa
name: csi-do-node-sa-luks
namespace: kube-system
roleRef:
kind: ClusterRole
name: csi-do-node-driver-registrar-role
name: csi-do-node-luks-driver-registrar-role
apiGroup: rbac.authorization.k8s.io
65 changes: 46 additions & 19 deletions driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const (
tiB
)

const (
// PublishInfoVolumeName is used to pass the volume name from
// `ControllerPublishVolume` to `NodeStageVolume or `NodePublishVolume`
PublishInfoVolumeName = DefaultDriverName + "/volume-name"
)

const (
// minimumVolumeSizeInBytes is used to validate that the user is not trying
// to create a volume that is smaller than what we support
Expand Down Expand Up @@ -112,12 +118,17 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}

volumeName := req.Name
luksEncrypted := "false"
if req.Parameters[LuksEncryptedAttribute] == "true" {
luksEncrypted = "true"
}

log := d.log.WithFields(logrus.Fields{
"volume_name": volumeName,
"storage_size_giga_bytes": size / giB,
"method": "create_volume",
"volume_capabilities": req.VolumeCapabilities,
"luks_encrypted": luksEncrypted,
})
log.Info("create volume called")

Expand All @@ -130,6 +141,26 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
return nil, status.Error(codes.Internal, err.Error())
}

csiVolume := csi.Volume{
AccessibleTopology: []*csi.Topology{
{
Segments: map[string]string{
"region": d.region,
},
},
},
CapacityBytes: size,
VolumeContext: map[string]string{
LuksEncryptedAttribute: luksEncrypted,
PublishInfoVolumeName: volumeName,
},
}

if luksEncrypted == "true" {
csiVolume.VolumeContext[LuksCipherAttribute] = req.Parameters[LuksCipherAttribute]
csiVolume.VolumeContext[LuksKeySizeAttribute] = req.Parameters[LuksKeySizeAttribute]
}

// volume already exist, do nothing
if len(volumes) != 0 {
if len(volumes) > 1 {
Expand All @@ -142,12 +173,9 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}

log.Info("volume already created")
return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: vol.ID,
CapacityBytes: vol.SizeGigaBytes * giB,
},
}, nil
csiVolume.VolumeId = vol.ID
csiVolume.CapacityBytes = vol.SizeGigaBytes * giB
return &csi.CreateVolumeResponse{Volume: &csiVolume}, nil
}

volumeReq := &godo.VolumeCreateRequest{
Expand Down Expand Up @@ -198,19 +226,8 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
return nil, status.Error(codes.Internal, err.Error())
}

resp := &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: vol.ID,
CapacityBytes: size,
AccessibleTopology: []*csi.Topology{
{
Segments: map[string]string{
"region": d.region,
},
},
},
},
}
csiVolume.VolumeId = vol.ID
resp := &csi.CreateVolumeResponse{Volume: &csiVolume}

// external-provisioner expects a content source to be returned if the PVC
// specified a data source, which corresponds to us having received a
Expand Down Expand Up @@ -327,6 +344,9 @@ func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controlle
return &csi.ControllerPublishVolumeResponse{
PublishContext: map[string]string{
d.publishInfoVolumeName: vol.Name,
LuksEncryptedAttribute: req.VolumeContext[LuksEncryptedAttribute],
LuksCipherAttribute: req.VolumeContext[LuksCipherAttribute],
LuksKeySizeAttribute: req.VolumeContext[LuksKeySizeAttribute],
},
}, nil
}
Expand All @@ -352,6 +372,9 @@ func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controlle
return &csi.ControllerPublishVolumeResponse{
PublishContext: map[string]string{
d.publishInfoVolumeName: vol.Name,
LuksEncryptedAttribute: req.VolumeContext[LuksEncryptedAttribute],
LuksCipherAttribute: req.VolumeContext[LuksCipherAttribute],
LuksKeySizeAttribute: req.VolumeContext[LuksKeySizeAttribute],
},
}, nil
}
Expand Down Expand Up @@ -383,6 +406,9 @@ func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controlle
return &csi.ControllerPublishVolumeResponse{
PublishContext: map[string]string{
d.publishInfoVolumeName: vol.Name,
LuksEncryptedAttribute: req.VolumeContext[LuksEncryptedAttribute],
LuksCipherAttribute: req.VolumeContext[LuksCipherAttribute],
LuksKeySizeAttribute: req.VolumeContext[LuksKeySizeAttribute],
},
}, nil
}
Expand Down Expand Up @@ -808,6 +834,7 @@ func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsReques

untypedSnapshots, nextToken, err := listResources(ctx, log, startingToken, req.MaxEntries, func(ctx context.Context, listOpts *godo.ListOptions) ([]interface{}, *godo.Response, error) {
snapshots, resp, err := d.snapshots.ListVolume(ctx, listOpts)

if err != nil {
return nil, resp, err
}
Expand Down
9 changes: 5 additions & 4 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,16 +514,16 @@ type fakeMounter struct {
mounted map[string]string
}

func (f *fakeMounter) Format(source string, fsType string) error {
func (f *fakeMounter) Format(source string, fsType string, context LuksContext) error {
return nil
}

func (f *fakeMounter) Mount(source string, target string, fsType string, options ...string) error {
func (f *fakeMounter) Mount(source string, target string, fsType string, context LuksContext, options ...string) error {
f.mounted[target] = source
return nil
}

func (f *fakeMounter) Unmount(target string) error {
func (f *fakeMounter) Unmount(target string, context LuksContext) error {
delete(f.mounted, target)
return nil
}
Expand All @@ -536,9 +536,10 @@ func (f *fakeMounter) GetDeviceName(_ mount.Interface, mountPath string) (string
return "", nil
}

func (f *fakeMounter) IsFormatted(source string) (bool, error) {
func (f *fakeMounter) IsFormatted(source string, context LuksContext) (bool, error) {
return true, nil
}

func (f *fakeMounter) IsMounted(target string) (bool, error) {
_, ok := f.mounted[target]
return ok, nil
Expand Down
Loading