Skip to content

Commit 083f615

Browse files
rbd: add EncryptionLoad2 implementing rbd_encryption_load2
Add a new Image method EncryptionLoad2 implementing rbd_encryption_load2. This method adds the ability to have different encryption schemes across parent images. Signed-off-by: John Mulligan <[email protected]> Fixes: ceph#1059
1 parent 8c89f46 commit 083f615

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed

Diff for: docs/api-status.json

+6
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,12 @@
19531953
"comment": "GroupSnapGetInfo returns a slice of RBD image snapshots that are part of a\ngroup snapshot.\n\nImplements:\n\n\tint rbd_group_snap_get_info(rados_ioctx_t group_p,\n\t const char *group_name,\n\t const char *snap_name,\n\t rbd_group_snap_info2_t *snaps);\n",
19541954
"added_in_version": "v0.30.0",
19551955
"expected_stable_version": "v0.32.0"
1956+
},
1957+
{
1958+
"name": "Image.EncryptionLoad2",
1959+
"comment": "EncryptionLoad2 enables IO on an open encrypted image. The difference\nbetween EncryptionLoad and EncryptionLoad2 is that EncryptionLoad2 can open\nancestor images with a different encryption options than the current image.\nThe first EncryptionOptions in the slice is applied to the current image,\nthe second to the first ancestor, the third to the second ancestor and so\non. If the length of the slice is smaller than the number of ancestors the\nfinal item in the slice will be applied to all remaining ancestors, or if\nthe ancestor does not match the encryption format the ancestor will be\ninterpreted as plain-text.\n\nImplements:\n\n\tint rbd_encryption_load2(rbd_image_t image,\n\t const rbd_encryption_spec_t *specs,\n\t size_t spec_count);\n",
1960+
"added_in_version": "$NEXT_RELEASE",
1961+
"expected_stable_version": "$NEXT_RELEASE_STABLE"
19561962
}
19571963
]
19581964
},

Diff for: docs/api-status.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Conn.GetAddrs | v0.31.0 | v0.33.0 |
2525
Name | Added in Version | Expected Stable Version |
2626
---- | ---------------- | ----------------------- |
2727
GroupSnapGetInfo | v0.30.0 | v0.32.0 |
28+
Image.EncryptionLoad2 | $NEXT_RELEASE | $NEXT_RELEASE_STABLE |
2829

2930
### Deprecated APIs
3031

Diff for: rbd/encryption_load2.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//go:build !octopus && !pacific && !quincy && ceph_preview
2+
3+
package rbd
4+
5+
// #cgo LDFLAGS: -lrbd
6+
// /* force XSI-complaint strerror_r() */
7+
// #define _POSIX_C_SOURCE 200112L
8+
// #undef _GNU_SOURCE
9+
// #include <rbd/librbd.h>
10+
import "C"
11+
12+
import (
13+
"unsafe"
14+
)
15+
16+
// toEncryptionSpec returns a rbd_encryption_spec_t converted from the
17+
// cEncryptionData type.
18+
func (edata cEncryptionData) toEncryptionSpec() C.rbd_encryption_spec_t {
19+
var cSpec C.rbd_encryption_spec_t
20+
cSpec.format = edata.format
21+
cSpec.opts = edata.opts
22+
cSpec.opts_size = edata.optsSize
23+
return cSpec
24+
}
25+
26+
// EncryptionLoad2 enables IO on an open encrypted image. The difference
27+
// between EncryptionLoad and EncryptionLoad2 is that EncryptionLoad2 can open
28+
// ancestor images with a different encryption options than the current image.
29+
// The first EncryptionOptions in the slice is applied to the current image,
30+
// the second to the first ancestor, the third to the second ancestor and so
31+
// on. If the length of the slice is smaller than the number of ancestors the
32+
// final item in the slice will be applied to all remaining ancestors, or if
33+
// the ancestor does not match the encryption format the ancestor will be
34+
// interpreted as plain-text.
35+
//
36+
// Implements:
37+
//
38+
// int rbd_encryption_load2(rbd_image_t image,
39+
// const rbd_encryption_spec_t *specs,
40+
// size_t spec_count);
41+
func (image *Image) EncryptionLoad2(opts []EncryptionOptions) error {
42+
if image.image == nil {
43+
return ErrImageNotOpen
44+
}
45+
46+
length := len(opts)
47+
eos := make([]cEncryptionData, length)
48+
cspecs := (*C.rbd_encryption_spec_t)(C.malloc(
49+
C.size_t(C.sizeof_rbd_encryption_spec_t * length)))
50+
specs := unsafe.Slice(cspecs, length)
51+
52+
for idx, option := range opts {
53+
eos[idx] = option.allocateEncryptionOptions()
54+
specs[idx] = eos[idx].toEncryptionSpec()
55+
}
56+
defer func() {
57+
for _, eopt := range eos {
58+
eopt.free()
59+
}
60+
}()
61+
62+
ret := C.rbd_encryption_load2(
63+
image.image,
64+
cspecs,
65+
C.size_t(length))
66+
return getError(ret)
67+
}

Diff for: rbd/encryption_load2_test.go

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//go:build !octopus && !pacific && !quincy && ceph_preview
2+
3+
package rbd
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestEncryptionLoad2(t *testing.T) {
13+
conn := radosConnect(t)
14+
defer conn.Shutdown()
15+
16+
poolname := GetUUID()
17+
err := conn.MakePool(poolname)
18+
assert.NoError(t, err)
19+
defer conn.DeletePool(poolname)
20+
21+
ioctx, err := conn.OpenIOContext(poolname)
22+
require.NoError(t, err)
23+
defer ioctx.Destroy()
24+
25+
name := GetUUID()
26+
testImageSize := uint64(50) * 1024 * 1024
27+
options := NewRbdImageOptions()
28+
assert.NoError(t,
29+
options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
30+
err = CreateImage(ioctx, name, testImageSize, options)
31+
assert.NoError(t, err)
32+
33+
img, err := OpenImage(ioctx, name, NoSnapshot)
34+
assert.NoError(t, err)
35+
36+
encOpts := EncryptionOptionsLUKS2{
37+
Alg: EncryptionAlgorithmAES256,
38+
Passphrase: []byte("test-password"),
39+
}
40+
err = img.EncryptionFormat(encOpts)
41+
assert.NoError(t, err)
42+
43+
// close the image so we can reopen it and load the encryption info
44+
// then write some encrypted data at the end of the image
45+
err = img.Close()
46+
assert.NoError(t, err)
47+
defer func() {
48+
assert.NoError(t, img.Remove())
49+
}()
50+
51+
testData := []byte("Jinxed wizards pluck ivy from the big quilt")
52+
var offset int64
53+
54+
t.Run("prepare", func(t *testing.T) {
55+
img, err = OpenImage(ioctx, name, NoSnapshot)
56+
assert.NoError(t, err)
57+
defer img.Close()
58+
err = img.EncryptionLoad2([]EncryptionOptions{encOpts})
59+
assert.NoError(t, err)
60+
61+
stats, err := img.Stat()
62+
require.NoError(t, err)
63+
offset = int64(stats.Size) - int64(len(testData))
64+
65+
nOut, err := img.WriteAt(testData, offset)
66+
assert.Equal(t, len(testData), nOut)
67+
assert.NoError(t, err)
68+
})
69+
70+
t.Run("readEnc", func(t *testing.T) {
71+
require.NotEqual(t, offset, 0)
72+
// Re-open the image, load the encryption format, and read the encrypted data
73+
img, err = OpenImage(ioctx, name, NoSnapshot)
74+
assert.NoError(t, err)
75+
defer img.Close()
76+
err = img.EncryptionLoad2([]EncryptionOptions{encOpts})
77+
assert.NoError(t, err)
78+
79+
inData := make([]byte, len(testData))
80+
nIn, err := img.ReadAt(inData, offset)
81+
assert.Equal(t, nIn, len(testData))
82+
assert.Equal(t, inData, testData)
83+
assert.NoError(t, err)
84+
})
85+
86+
t.Run("noEnc", func(t *testing.T) {
87+
require.NotEqual(t, offset, 0)
88+
// Re-open the image and attempt to read the encrypted data without loading the encryption
89+
img, err = OpenImage(ioctx, name, NoSnapshot)
90+
assert.NoError(t, err)
91+
defer img.Close()
92+
93+
inData := make([]byte, len(testData))
94+
nIn, err := img.ReadAt(inData, offset)
95+
assert.Equal(t, nIn, len(testData))
96+
assert.NotEqual(t, inData, testData)
97+
assert.NoError(t, err)
98+
})
99+
}
100+
101+
func TestEncryptionLoad2WithParents(t *testing.T) {
102+
dlength := int64(32)
103+
testData1 := []byte("Very nice object ahead of change")
104+
testData2 := []byte("A nice object encryption applied")
105+
testData3 := []byte("A good object encryption abounds")
106+
testData4 := []byte("Another portion is here and well")
107+
written := [][]byte{}
108+
assert.EqualValues(t, len(testData1), dlength)
109+
assert.EqualValues(t, len(testData2), dlength)
110+
assert.EqualValues(t, len(testData3), dlength)
111+
assert.EqualValues(t, len(testData4), dlength)
112+
113+
encOpts1 := EncryptionOptionsLUKS1{
114+
Alg: EncryptionAlgorithmAES128,
115+
Passphrase: []byte("test-password"),
116+
}
117+
encOpts2 := EncryptionOptionsLUKS2{
118+
Alg: EncryptionAlgorithmAES128,
119+
Passphrase: []byte("test-password"),
120+
}
121+
encOpts3 := EncryptionOptionsLUKS2{
122+
Alg: EncryptionAlgorithmAES256,
123+
Passphrase: []byte("something-stronger"),
124+
}
125+
126+
conn := radosConnect(t)
127+
defer conn.Shutdown()
128+
129+
poolname := GetUUID()
130+
err := conn.MakePool(poolname)
131+
assert.NoError(t, err)
132+
defer conn.DeletePool(poolname)
133+
134+
ioctx, err := conn.OpenIOContext(poolname)
135+
require.NoError(t, err)
136+
defer ioctx.Destroy()
137+
138+
name := GetUUID()
139+
testImageSize := uint64(256) * 1024 * 1024
140+
options := NewRbdImageOptions()
141+
assert.NoError(t,
142+
options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
143+
err = CreateImage(ioctx, name, testImageSize, options)
144+
assert.NoError(t, err)
145+
146+
t.Run("prepare", func(t *testing.T) {
147+
img, err := OpenImage(ioctx, name, NoSnapshot)
148+
assert.NoError(t, err)
149+
defer img.Close()
150+
151+
_, err = img.WriteAt(testData1, 0)
152+
assert.NoError(t, err)
153+
written = append(written, testData1)
154+
})
155+
156+
t.Run("createClone1", func(t *testing.T) {
157+
require.Len(t, written, 1)
158+
parent, err := OpenImage(ioctx, name, NoSnapshot)
159+
assert.NoError(t, err)
160+
defer parent.Close()
161+
snap, err := parent.CreateSnapshot("sn1")
162+
assert.NoError(t, err)
163+
err = snap.Protect()
164+
assert.NoError(t, err)
165+
166+
err = CloneImage(ioctx, name, "sn1", ioctx, name+"clone1", options)
167+
assert.NoError(t, err)
168+
169+
img, err := OpenImage(ioctx, name+"clone1", NoSnapshot)
170+
assert.NoError(t, err)
171+
defer img.Close()
172+
err = img.EncryptionFormat(encOpts1)
173+
assert.NoError(t, err)
174+
175+
err = img.EncryptionLoad2([]EncryptionOptions{encOpts1})
176+
assert.NoError(t, err)
177+
_, err = img.WriteAt(testData2, dlength)
178+
assert.NoError(t, err)
179+
written = append(written, testData2)
180+
})
181+
182+
t.Run("createClone2", func(t *testing.T) {
183+
require.Len(t, written, 2)
184+
parentName := name + "clone1"
185+
cloneName := name + "clone2"
186+
187+
parent, err := OpenImage(ioctx, parentName, NoSnapshot)
188+
assert.NoError(t, err)
189+
defer parent.Close()
190+
snap, err := parent.CreateSnapshot("sn2")
191+
assert.NoError(t, err)
192+
err = snap.Protect()
193+
assert.NoError(t, err)
194+
195+
err = CloneImage(ioctx, parentName, "sn2", ioctx, cloneName, options)
196+
assert.NoError(t, err)
197+
198+
img, err := OpenImage(ioctx, cloneName, NoSnapshot)
199+
assert.NoError(t, err)
200+
defer img.Close()
201+
err = img.EncryptionFormat(encOpts2)
202+
assert.NoError(t, err)
203+
204+
err = img.EncryptionLoad2([]EncryptionOptions{encOpts2, encOpts1})
205+
assert.NoError(t, err)
206+
_, err = img.WriteAt(testData3, dlength*2)
207+
assert.NoError(t, err)
208+
written = append(written, testData3)
209+
})
210+
211+
t.Run("createClone3", func(t *testing.T) {
212+
require.Len(t, written, 3)
213+
parentName := name + "clone2"
214+
cloneName := name + "clone3"
215+
216+
parent, err := OpenImage(ioctx, parentName, NoSnapshot)
217+
assert.NoError(t, err)
218+
defer parent.Close()
219+
snap, err := parent.CreateSnapshot("sn3")
220+
assert.NoError(t, err)
221+
err = snap.Protect()
222+
assert.NoError(t, err)
223+
224+
err = CloneImage(ioctx, parentName, "sn3", ioctx, cloneName, options)
225+
assert.NoError(t, err)
226+
227+
img, err := OpenImage(ioctx, cloneName, NoSnapshot)
228+
assert.NoError(t, err)
229+
defer img.Close()
230+
err = img.EncryptionFormat(encOpts3)
231+
assert.NoError(t, err)
232+
233+
err = img.EncryptionLoad2([]EncryptionOptions{
234+
encOpts3, encOpts2, encOpts1,
235+
})
236+
assert.NoError(t, err)
237+
_, err = img.WriteAt(testData4, dlength*3)
238+
assert.NoError(t, err)
239+
written = append(written, testData4)
240+
})
241+
242+
t.Run("readAll", func(t *testing.T) {
243+
require.Len(t, written, 4)
244+
img, err := OpenImage(ioctx, name+"clone3", NoSnapshot)
245+
assert.NoError(t, err)
246+
defer img.Close()
247+
248+
err = img.EncryptionLoad2([]EncryptionOptions{
249+
encOpts3, encOpts2, encOpts1,
250+
})
251+
assert.NoError(t, err)
252+
253+
inData := make([]byte, int(dlength))
254+
for idx, td := range written {
255+
n, err := img.ReadAt(inData, int64(idx)*dlength)
256+
assert.NoError(t, err)
257+
assert.EqualValues(t, dlength, n)
258+
assert.Equal(t, inData, td)
259+
}
260+
})
261+
}

0 commit comments

Comments
 (0)