Repository: ceph/go-ceph
Pull request #542 (2021-07-27 09:13:37)

rbd: add EncryptionLoad api to support the rbd_encryption_load librbd…
rbd: add EncryptionLoad api to support the rbd_encryption_load librbd api.

EncryptionLoad loads the encryption format information for an image and enables encrypted IO.  This is a new feature in Ceph Pacific.

Signed-off-by: Effi Ofer <effio@il.ibm.com>


<!--
Thank you for opening a pull request. Please provide:

- A clear summary of your changes

- Descriptive and succinct commit messages with the format:
  """
  [topic]: [short description]

  [Longer description]

  Signed-off-by: [Your Name] <[your email]>
  """

  Topic will generally be the go ceph package dir you are working in.

- Ensure checklist items listed below are accounted for
-->

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code


effi-ofer: > One more small nitpick about the tests: you write the data with encryption enabled, and read it back from the same open image. I think it would be better for verification to close the image after writing and then reopen it with encryption enabled. That way we're unlikely to be hitting any caches, etc.
> 
> If you don't think this is a good idea, please let me know. Otherwise things are looking good!

Done
effi-ofer: @phlogistonjohn thanks for the review.  Any idea why the test-suite is failing?
phlogistonjohn: > @phlogistonjohn thanks for the review. Any idea why the test-suite is failing?

Unfortunately, no. It seems like it could be a time out. It certainly was the first time, but the most recent time as of this writing is more mysterious. I've started the CI again. If if fails again in a similar way I'll investigate deeper, but I'm hoping we're just getting unlucky with test flakes.
phlogistonjohn: Good news: it looks like we did just get unlucky with the previous two runs, the CI has passed now.
effi-ofer: > Good news: it looks like we did just get unlucky with the previous two runs, the CI has passed now.

That's awesome and thank you @phlogistonjohn.  What do we need to do next to get this merged?
phlogistonjohn: > > Good news: it looks like we did just get unlucky with the previous two runs, the CI has passed now.
> 
> That's awesome and thank you @phlogistonjohn. What do we need to do next to get this merged?

A second approving review. Please be patient, as most of our maintainers do go-ceph work as part of their jobs and I know some people are unavailable today.
effi-ofer: > > > Good news: it looks like we did just get unlucky with the previous two runs, the CI has passed now.
> > 
> > 
> > That's awesome and thank you @phlogistonjohn. What do we need to do next to get this merged?
> 
> A second approving review. Please be patient, as most of our maintainers do go-ceph work as part of their jobs and I know some people are unavailable today.

No rush.  And thanks for responding so quickly.
phlogistonjohn: @mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch has been successfully rebased**
> 


Patch:
diff --git a/rbd/encryption.go b/rbd/encryption.go
index 4b1a6d0..dfcb8b8 100644
--- a/rbd/encryption.go
+++ b/rbd/encryption.go
@@ -16,8 +16,8 @@ import (
 	"unsafe"
 )
 
-// cData contains the data needed by the encryption functions
-type cData struct {
+// cEncryptionData contains the data needed by the encryption functions
+type cEncryptionData struct {
 	format   C.rbd_encryption_format_t
 	opts     C.rbd_encryption_options_t
 	optsSize C.size_t
@@ -56,12 +56,12 @@ type EncryptionOptionsLUKS2 struct {
 // EncryptionOptions interface is used to encapsulate the different encryption
 // formats options and enable converting them from go to C structures.
 type EncryptionOptions interface {
-	allocateEncryptionOptions() cData
+	allocateEncryptionOptions() cEncryptionData
 }
 
-func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cData {
+func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cEncryptionData {
 	var cOpts C.rbd_encryption_luks1_format_options_t
-	var retData cData
+	var retData cEncryptionData
 	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
 	//CBytes allocates memory which we'll free by calling cOptsFree()
 	cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase))
@@ -73,9 +73,9 @@ func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cData {
 	return retData
 }
 
-func (opts EncryptionOptionsLUKS2) allocateEncryptionOptions() cData {
+func (opts EncryptionOptionsLUKS2) allocateEncryptionOptions() cEncryptionData {
 	var cOpts C.rbd_encryption_luks2_format_options_t
-	var retData cData
+	var retData cEncryptionData
 	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
 	//CBytes allocates memory which we'll free by calling cOptsFree()
 	cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase))
@@ -114,3 +114,26 @@ func (image *Image) EncryptionFormat(opts EncryptionOptions) error {
 
 	return getError(ret)
 }
+
+// EncryptionLoad enables IO on an open encrypted image
+//
+// Implements:
+//  int rbd_encryption_load(rbd_image_t image,
+//                          rbd_encryption_format_t format,
+//                          rbd_encryption_options_t opts,
+//                          size_t opts_size);
+func (image *Image) EncryptionLoad(opts EncryptionOptions) error {
+	if image.image == nil {
+		return ErrImageNotOpen
+	}
+
+	encryptionOpts := opts.allocateEncryptionOptions()
+	defer encryptionOpts.free()
+
+	ret := C.rbd_encryption_load(
+		image.image,
+		encryptionOpts.format,
+		encryptionOpts.opts,
+		encryptionOpts.optsSize)
+	return getError(ret)
+}


Test patch:
diff --git a/rbd/encryption_test.go b/rbd/encryption_test.go
index 8bad7ce..7a55c25 100644
--- a/rbd/encryption_test.go
+++ b/rbd/encryption_test.go
@@ -45,3 +45,85 @@ func TestEncryptionFormat(t *testing.T) {
 	conn.DeletePool(poolname)
 	conn.Shutdown()
 }
+
+func TestEncryptionLoad(t *testing.T) {
+	conn := radosConnect(t)
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	assert.NoError(t, err)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+
+	name := GetUUID()
+	testImageSize := uint64(1 << 23) // format requires more than 4194304 bytes
+	options := NewRbdImageOptions()
+	assert.NoError(t,
+		options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
+	err = CreateImage(ioctx, name, testImageSize, options)
+	assert.NoError(t, err)
+
+	img, err := OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+
+	var opts EncryptionOptionsLUKS1
+	opts.Alg = EncryptionAlgorithmAES256
+	opts.Passphrase = ([]byte)("test-password")
+	err = img.EncryptionFormat(opts)
+	assert.NoError(t, err)
+
+	// close the image so we can reopen it and load the encryption info
+	// then write some encrypted data at the end of the image
+	err = img.Close()
+	assert.NoError(t, err)
+	img, err = OpenImage(ioctx, name, NoSnapshot)
+	err = img.EncryptionLoad(opts)
+	assert.NoError(t, err)
+
+	outData := []byte("Hi rbd! Nice to talk through go-ceph :)")
+
+	stats, err := img.Stat()
+	require.NoError(t, err)
+	offset := int64(stats.Size) - int64(len(outData))
+
+	nOut, err := img.WriteAt(outData, offset)
+	assert.Equal(t, len(outData), nOut)
+	assert.NoError(t, err)
+
+	err = img.Close()
+	assert.NoError(t, err)
+
+	// Re-open the image, load the encryption format, and read the encrypted data
+	img, err = OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+	err = img.EncryptionLoad(opts)
+	assert.NoError(t, err)
+
+	inData := make([]byte, len(outData))
+	nIn, err := img.ReadAt(inData, offset)
+	assert.Equal(t, nIn, len(inData))
+	assert.Equal(t, inData, outData)
+	assert.NoError(t, err)
+
+	err = img.Close()
+	assert.NoError(t, err)
+
+	// Re-open the image and attempt to read the encrypted data without loading the encryption
+	img, err = OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+
+	nIn, err = img.ReadAt(inData, offset)
+	assert.Equal(t, nIn, len(inData))
+	assert.NotEqual(t, inData, outData)
+	assert.NoError(t, err)
+
+	err = img.Close()
+	assert.NoError(t, err)
+	err = img.Remove()
+	assert.NoError(t, err)
+
+	ioctx.Destroy()
+	conn.DeletePool(poolname)
+	conn.Shutdown()
+}