-
Notifications
You must be signed in to change notification settings - Fork 260
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
rbd: add EncryptionLoad2 implementing rbd_encryption_load2 #1061
base: master
Are you sure you want to change the base?
Changes from all commits
0ef3da2
8c89f46
083f615
bef69a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,7 @@ func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cEncryptionData { | |
var cOpts C.rbd_encryption_luks1_format_options_t | ||
var retData cEncryptionData | ||
cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg) | ||
//CBytes allocates memory which we'll free by calling cOptsFree() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was just outdated? Because I don't see any change regarding |
||
// CBytes allocates memory. it will be freed when cEncryptionData.free is called | ||
cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase)) | ||
cOpts.passphrase_size = C.size_t(len(opts.Passphrase)) | ||
retData.opts = C.rbd_encryption_options_t(&cOpts) | ||
|
@@ -78,7 +78,7 @@ func (opts EncryptionOptionsLUKS2) allocateEncryptionOptions() cEncryptionData { | |
var cOpts C.rbd_encryption_luks2_format_options_t | ||
var retData cEncryptionData | ||
cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg) | ||
//CBytes allocates memory which we'll free by calling cOptsFree() | ||
// CBytes allocates memory. it will be freed when cEncryptionData.free is called | ||
cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase)) | ||
cOpts.passphrase_size = C.size_t(len(opts.Passphrase)) | ||
retData.opts = C.rbd_encryption_options_t(&cOpts) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
//go:build !octopus && !pacific && !quincy && ceph_preview | ||
|
||
package rbd | ||
|
||
// #cgo LDFLAGS: -lrbd | ||
// /* force XSI-complaint strerror_r() */ | ||
// #define _POSIX_C_SOURCE 200112L | ||
// #undef _GNU_SOURCE | ||
// #include <rbd/librbd.h> | ||
import "C" | ||
|
||
import ( | ||
"unsafe" | ||
) | ||
|
||
// toEncryptionSpec returns a rbd_encryption_spec_t converted from the | ||
// cEncryptionData type. | ||
func (edata cEncryptionData) toEncryptionSpec() C.rbd_encryption_spec_t { | ||
var cSpec C.rbd_encryption_spec_t | ||
cSpec.format = edata.format | ||
cSpec.opts = edata.opts | ||
cSpec.opts_size = edata.optsSize | ||
return cSpec | ||
} | ||
|
||
// EncryptionLoad2 enables IO on an open encrypted image. The difference | ||
// between EncryptionLoad and EncryptionLoad2 is that EncryptionLoad2 can open | ||
// ancestor images with a different encryption options than the current image. | ||
// The first EncryptionOptions in the slice is applied to the current image, | ||
// the second to the first ancestor, the third to the second ancestor and so | ||
// on. If the length of the slice is smaller than the number of ancestors the | ||
// final item in the slice will be applied to all remaining ancestors, or if | ||
// the ancestor does not match the encryption format the ancestor will be | ||
// interpreted as plain-text. | ||
// | ||
// Implements: | ||
// | ||
// int rbd_encryption_load2(rbd_image_t image, | ||
// const rbd_encryption_spec_t *specs, | ||
// size_t spec_count); | ||
func (image *Image) EncryptionLoad2(opts []EncryptionOptions) error { | ||
if image.image == nil { | ||
return ErrImageNotOpen | ||
} | ||
|
||
length := len(opts) | ||
eos := make([]cEncryptionData, length) | ||
cspecs := (*C.rbd_encryption_spec_t)(C.malloc( | ||
C.size_t(C.sizeof_rbd_encryption_spec_t * length))) | ||
specs := unsafe.Slice(cspecs, length) | ||
|
||
for idx, option := range opts { | ||
eos[idx] = option.allocateEncryptionOptions() | ||
idryomov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
specs[idx] = eos[idx].toEncryptionSpec() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here for sure is a problem. It took me quite a while to find that, because how the code is structured and named, it was not clear at all to me, what is C allocated and what not. First I thought everything that starts with a |
||
} | ||
defer func() { | ||
for _, eopt := range eos { | ||
eopt.free() | ||
} | ||
}() | ||
|
||
ret := C.rbd_encryption_load2( | ||
image.image, | ||
cspecs, | ||
C.size_t(length)) | ||
return getError(ret) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
//go:build !octopus && !pacific && !quincy && ceph_preview | ||
|
||
package rbd | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEncryptionLoad2(t *testing.T) { | ||
conn := radosConnect(t) | ||
defer conn.Shutdown() | ||
|
||
poolname := GetUUID() | ||
err := conn.MakePool(poolname) | ||
assert.NoError(t, err) | ||
defer conn.DeletePool(poolname) | ||
|
||
ioctx, err := conn.OpenIOContext(poolname) | ||
require.NoError(t, err) | ||
defer ioctx.Destroy() | ||
|
||
name := GetUUID() | ||
testImageSize := uint64(50) * 1024 * 1024 | ||
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) | ||
|
||
encOpts := EncryptionOptionsLUKS2{ | ||
Alg: EncryptionAlgorithmAES256, | ||
Passphrase: []byte("test-password"), | ||
} | ||
err = img.EncryptionFormat(encOpts) | ||
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) | ||
defer func() { | ||
assert.NoError(t, img.Remove()) | ||
}() | ||
|
||
testData := []byte("Jinxed wizards pluck ivy from the big quilt") | ||
var offset int64 | ||
|
||
t.Run("prepare", func(t *testing.T) { | ||
img, err = OpenImage(ioctx, name, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
err = img.EncryptionLoad2([]EncryptionOptions{encOpts}) | ||
assert.NoError(t, err) | ||
|
||
stats, err := img.Stat() | ||
require.NoError(t, err) | ||
offset = int64(stats.Size) - int64(len(testData)) | ||
|
||
nOut, err := img.WriteAt(testData, offset) | ||
assert.Equal(t, len(testData), nOut) | ||
assert.NoError(t, err) | ||
}) | ||
|
||
t.Run("readEnc", func(t *testing.T) { | ||
require.NotEqual(t, offset, 0) | ||
// Re-open the image, load the encryption format, and read the encrypted data | ||
img, err = OpenImage(ioctx, name, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
err = img.EncryptionLoad2([]EncryptionOptions{encOpts}) | ||
assert.NoError(t, err) | ||
|
||
inData := make([]byte, len(testData)) | ||
nIn, err := img.ReadAt(inData, offset) | ||
assert.Equal(t, nIn, len(testData)) | ||
assert.Equal(t, inData, testData) | ||
assert.NoError(t, err) | ||
}) | ||
|
||
t.Run("noEnc", func(t *testing.T) { | ||
require.NotEqual(t, offset, 0) | ||
// 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) | ||
defer img.Close() | ||
|
||
inData := make([]byte, len(testData)) | ||
nIn, err := img.ReadAt(inData, offset) | ||
assert.Equal(t, nIn, len(testData)) | ||
assert.NotEqual(t, inData, testData) | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
|
||
func TestEncryptionLoad2WithParents(t *testing.T) { | ||
dlength := int64(32) | ||
testData1 := []byte("Very nice object ahead of change") | ||
testData2 := []byte("A nice object encryption applied") | ||
testData3 := []byte("A good object encryption abounds") | ||
testData4 := []byte("Another portion is here and well") | ||
written := [][]byte{} | ||
assert.EqualValues(t, len(testData1), dlength) | ||
assert.EqualValues(t, len(testData2), dlength) | ||
assert.EqualValues(t, len(testData3), dlength) | ||
assert.EqualValues(t, len(testData4), dlength) | ||
|
||
encOpts1 := EncryptionOptionsLUKS1{ | ||
Alg: EncryptionAlgorithmAES128, | ||
Passphrase: []byte("test-password"), | ||
} | ||
encOpts2 := EncryptionOptionsLUKS2{ | ||
Alg: EncryptionAlgorithmAES128, | ||
Passphrase: []byte("test-password"), | ||
} | ||
encOpts3 := EncryptionOptionsLUKS2{ | ||
Alg: EncryptionAlgorithmAES256, | ||
Passphrase: []byte("something-stronger"), | ||
} | ||
|
||
conn := radosConnect(t) | ||
defer conn.Shutdown() | ||
|
||
poolname := GetUUID() | ||
err := conn.MakePool(poolname) | ||
assert.NoError(t, err) | ||
defer conn.DeletePool(poolname) | ||
|
||
ioctx, err := conn.OpenIOContext(poolname) | ||
require.NoError(t, err) | ||
defer ioctx.Destroy() | ||
|
||
name := GetUUID() | ||
testImageSize := uint64(256) * 1024 * 1024 | ||
options := NewRbdImageOptions() | ||
assert.NoError(t, | ||
options.SetUint64(ImageOptionOrder, uint64(testImageOrder))) | ||
err = CreateImage(ioctx, name, testImageSize, options) | ||
assert.NoError(t, err) | ||
|
||
t.Run("prepare", func(t *testing.T) { | ||
img, err := OpenImage(ioctx, name, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
|
||
_, err = img.WriteAt(testData1, 0) | ||
assert.NoError(t, err) | ||
written = append(written, testData1) | ||
}) | ||
|
||
t.Run("createClone1", func(t *testing.T) { | ||
require.Len(t, written, 1) | ||
parent, err := OpenImage(ioctx, name, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer parent.Close() | ||
snap, err := parent.CreateSnapshot("sn1") | ||
assert.NoError(t, err) | ||
err = snap.Protect() | ||
assert.NoError(t, err) | ||
|
||
err = CloneImage(ioctx, name, "sn1", ioctx, name+"clone1", options) | ||
assert.NoError(t, err) | ||
|
||
img, err := OpenImage(ioctx, name+"clone1", NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
err = img.EncryptionFormat(encOpts1) | ||
assert.NoError(t, err) | ||
|
||
err = img.EncryptionLoad2([]EncryptionOptions{encOpts1}) | ||
assert.NoError(t, err) | ||
_, err = img.WriteAt(testData2, dlength) | ||
assert.NoError(t, err) | ||
written = append(written, testData2) | ||
}) | ||
|
||
t.Run("createClone2", func(t *testing.T) { | ||
require.Len(t, written, 2) | ||
parentName := name + "clone1" | ||
cloneName := name + "clone2" | ||
|
||
parent, err := OpenImage(ioctx, parentName, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer parent.Close() | ||
snap, err := parent.CreateSnapshot("sn2") | ||
assert.NoError(t, err) | ||
err = snap.Protect() | ||
assert.NoError(t, err) | ||
|
||
err = CloneImage(ioctx, parentName, "sn2", ioctx, cloneName, options) | ||
assert.NoError(t, err) | ||
|
||
img, err := OpenImage(ioctx, cloneName, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
err = img.EncryptionFormat(encOpts2) | ||
assert.NoError(t, err) | ||
|
||
err = img.EncryptionLoad2([]EncryptionOptions{encOpts2, encOpts1}) | ||
assert.NoError(t, err) | ||
_, err = img.WriteAt(testData3, dlength*2) | ||
assert.NoError(t, err) | ||
written = append(written, testData3) | ||
}) | ||
|
||
t.Run("createClone3", func(t *testing.T) { | ||
require.Len(t, written, 3) | ||
parentName := name + "clone2" | ||
cloneName := name + "clone3" | ||
|
||
parent, err := OpenImage(ioctx, parentName, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer parent.Close() | ||
snap, err := parent.CreateSnapshot("sn3") | ||
assert.NoError(t, err) | ||
err = snap.Protect() | ||
assert.NoError(t, err) | ||
|
||
err = CloneImage(ioctx, parentName, "sn3", ioctx, cloneName, options) | ||
assert.NoError(t, err) | ||
|
||
img, err := OpenImage(ioctx, cloneName, NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
err = img.EncryptionFormat(encOpts3) | ||
assert.NoError(t, err) | ||
|
||
err = img.EncryptionLoad2([]EncryptionOptions{ | ||
encOpts3, encOpts2, encOpts1, | ||
}) | ||
assert.NoError(t, err) | ||
_, err = img.WriteAt(testData4, dlength*3) | ||
assert.NoError(t, err) | ||
written = append(written, testData4) | ||
}) | ||
|
||
t.Run("readAll", func(t *testing.T) { | ||
require.Len(t, written, 4) | ||
img, err := OpenImage(ioctx, name+"clone3", NoSnapshot) | ||
assert.NoError(t, err) | ||
defer img.Close() | ||
|
||
err = img.EncryptionLoad2([]EncryptionOptions{ | ||
encOpts3, encOpts2, encOpts1, | ||
}) | ||
assert.NoError(t, err) | ||
|
||
inData := make([]byte, int(dlength)) | ||
for idx, td := range written { | ||
n, err := img.ReadAt(inData, int64(idx)*dlength) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, dlength, n) | ||
assert.Equal(t, inData, td) | ||
} | ||
}) | ||
} | ||
anoopcs9 marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... only if the encryption format is not known to librbd at all. If e.g. LUKS1 is specified in
EncryptionOptions
that is being reused but the image has a LUKS2 header (i.e. known to librbd),EncryptionLoad2
would fail. I'd suggest copyinglibrbd.h
comment as close as possible -- don't omit anything and make adjustments only for encryption spec ->EncryptionOptions
, array -> slice, etc.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or alternatively don't go into details -- keeping just the first two sentences would be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, ok. I'll update this later to err on the side of being vague.