Adds per volume encryption with Vault integration

- adds proposal document for PVC encryption from PR448
- adds per-volume encription by generating encryption passphrase
  for each volume and storing it in a KMS
- adds HashiCorp Vault integration as a KMS for encryption passphrases
- avoids encrypting volume second time if it was already encrypted but
  no file system created
- avoids unnecessary checks if volume is a mapped device when encryption
  was not requested
- prevents resizing encrypted volumes (it is not currently supported)
- prevents creating snapshots from encrypted volumes to prevent attack
  on encryption key (security guard until re-encryption of volumes
  implemented)

Signed-off-by: Vasyl Purchel vasyl.purchel@workday.com
Signed-off-by: Andrea Baglioni andrea.baglioni@workday.com

Fixes #420
Fixes #744
This commit is contained in:
Vasyl Purchel
2020-01-29 11:44:45 +00:00
committed by mergify[bot]
parent 1adef00c86
commit 419ad0dd8e
26 changed files with 1210 additions and 102 deletions

View File

@ -1,4 +1,3 @@
# CSI RBD Plugin
The RBD CSI plugin is able to provision new RBD images and
@ -55,6 +54,8 @@ make image-cephcsi
| `csi.storage.k8s.io/provisioner-secret-namespace`, `csi.storage.k8s.io/node-stage-secret-namespace` | yes (for Kubernetes) | namespaces of the above Secret objects |
| `mounter` | no | if set to `rbd-nbd`, use `rbd-nbd` on nodes that have `rbd-nbd` and `nbd` kernel modules to map rbd images |
| `encrypted` | no | disabled by default, use `"true"` to enable LUKS encryption on pvc and `"false"` to disable it. **Do not change for existing storageclasses** |
| `encryptionKMS` | no | specifies key management system for encrypytion. Currently supports `vault` |
| `encryptionKMSID` | no | required if `encryptionKMS` is set to `vault` to specify a unique identifier for vault configuration |
**NOTE:** An accompanying CSI configuration file, needs to be provided to the
running pods. Refer to [Creating CSI configuration](../examples/README.md#creating-csi-configuration)
@ -196,8 +197,10 @@ and `csi.storage.k8s.io/node-stage-secret-namespace`).
* volume is attached to provisioner container
* on first time attachment
(no file system on the attached device, checked with blkid)
* new passphrase is generated and stored in selected KMS if KMS is in use
* device is encrypted with LUKS using a passphrase from K8s secrets
* image-meta updated to "encrypted" in Ceph
* passphrase is retrieved from selected KMS if KMS is in use
* device is open and device path is changed to use a mapper device
* mapper device is used instead of original one with usual workflow
@ -205,6 +208,7 @@ and `csi.storage.k8s.io/node-stage-secret-namespace`).
* mapper device closed and device path changed to original volume path
* volume is detached as usual
* passphrase removed from KMS if needed (with failures ignored)
### Encryption configuration
@ -213,6 +217,38 @@ secrets under `encryptionPassphrase` key and switch `encrypted` option in
StorageClass to `"true"`. This is not supported for storage classes that already
have PVs provisioned.
### Encryption KMS configuration
To further improve security robustness it is possible to use unique passphrases
generated for each volume and stored in a Key Management System (KMS). Currently
HashiCorp Vault is the only KMS supported.
To use Vault as KMS set `encryptionKMS` to `vault` and `encryptionKMSID` to a
unique identifier for Vault configuration. You will also need to create vault
configuration similar to the [example](../examples/rbd/kms-config.yaml)
and use same `encryptionKMSID`. In order for ceph-csi to be able to access the
configuration you will need to have it mounted to csi-rbdplugin containers in
both daemonset (so kms client can be instantiated to encrypt/decrypt volumes)
and deployment pods (so kms client can be instantiated to delete passphrase on
volume delete) `ceph-csi-encryption-kms-config` config map.
#### Configuring HashiCorp Vault
Using Vault as KMS you need to configure Kubernetes authentication method as
described in [official
documentation](https://www.vaultproject.io/docs/auth/kubernetes.html).
If token reviewer is used, you will need to configure service account for
that also like in [example](../examples/rbd/csi-vaulttokenreview-rbac.yaml) to
be able to review jwt tokens.
Configure a role(s) for service accounts used for ceph-csi:
* provisioner service account (`rbd-csi-provisioner`) requires only **delete**
permissions to delete passphrases on pvc delete
* nodeplugin service account (`rbd-csi-nodeplugin`) requires **create** and
**read** permissions to save new keys and retrieve existing
### Encryption prerequisites
In order for encryption to work you need to make sure that `dm-crypt` kernel

View File

@ -0,0 +1,131 @@
# Encrypted Persistent Volume Claims
## Proposal
Subject of this proposal is to add support for encryption of RBD volumes in
Ceph-CSI.
Some but not all the benefits of this approach:
* guarantee encryption in transit to rbd without using messenger v2
* extra security layer to application with special regulatory needs
* at rest encryption can be disabled to selectively allow encryption only where
required
## Document Terminology
* volume encryption: encryption of a volume attached by rbd
* encryption at rest: encryption of physical disk done by ceph
* LUKS: Linux Unified Key Setup: stores all of the needed setup information for
dm-crypt on the disk
* dm-crypt: linux kernel device-mapper crypto target
* cryptsetup: the command line tool to interface with dm-crypt
## Proposed Solution
The proposed solution in this document, is to address the volume encryption
requirement by using dm-crypt module through cryptsetup cli interface.
### Implementation Summary
* Encryption is implemented using cryptsetup with LUKS extension.
A good introduction to LUKS and dm-crypt in general can be found
[here](https://wiki.archlinux.org/index.php/Dm-crypt/Device_encryption#Encrypting_devices_with_cryptsetup)
Functions to implement necessary interaction are implemented in a separate
`cryptsetup.go` file.
* LuksFormat
* LuksOpen
* LuksClose
* LuksStatus
* `CreateVolume`: refactored to prepare for encryption (tag image that it
requires encryption later), before returning, if encrypted volume option is
set.
* `NodeStageVolume`: refactored to call `encryptDevice` method on the very first
volume attach request
* `NodeStageVolume`: refactored to open encrypted device (`openEncryptedDevice`)
* `openEncryptedDevice`: looks up for a passphrase matching the volume id,
returns the new device path in the form: `/dev/mapper/luks-<volume_id>`.
On the woker node where the attach is scheduled:
```shell
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 10G 0 disk
└─sda1 8:1 0 10G 0 part /
sdb 8:16 0 20G 0 disk
rbd0 253:0 0 1G 0 disk
└─luks-pvc-8a710f4c934811e9 252:0 0 1020M 0 crypt /var/lib/kubelet/pods/9eaceaef-936c-11e9-b396-005056af3de0/volumes/kubernetes.io~csi/pvc-8a710f4c934811e9/mount
```
* `detachRBDDevice`: calls `LuksClose` function to remove the LUKS mapping
before detaching the volume.
* StorageClass extended with following parameters:
1. `encrypted` ("true" or "false")
1. `encryptionKMS` (string representing kms of choice)
ceph-csi plugin may support different kms vendors with different type of
authentication
1. `encryptionKMSID` (string representing kms configuration)
* New KMS Configuration created.
#### Annotated YAML for RBD StorageClass
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-rbd
provisioner: rbd.csi.ceph.com
parameters:
# String representing Ceph cluster configuration
clusterID: <cluster-id>
# ceph pool
pool: rbd
# RBD image features, CSI creates image with image-format 2
# CSI RBD currently supports only `layering` feature.
imageFeatures: layering
# The secrets have to contain Ceph credentials with required access
# to the 'pool'.
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
csi.storage.k8s.io/controller-expand-secret-namespace: default
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: default
# Specify the filesystem type of the volume. If not specified,
# csi-provisioner will set default as `ext4`.
csi.storage.k8s.io/fstype: ext4
# Encrypt volumes
encrypted: "true"
# The type of kms we want to connect to: Barbican, aws kms or others can be
# supported
encryptionKMS: vault
# String representing a KMS configuration
encryptionKMSID: <kms-id>
reclaimPolicy: Delete
```
And kms configuration:
```yaml
---
apiVersion: v1
kind: ConfigMap
data:
config.json: |-
[
{
"kmsID": "<kms-id>",
kms specific config...
}
]
metadata:
name: ceph-csi-encryption-kms-config
```