Skip to content

Commit ccb22a5

Browse files
phlogistonjohnmergify[bot]
authored andcommitted
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: #1059
1 parent 6bc3fb2 commit ccb22a5

File tree

4 files changed

+373
-0
lines changed

4 files changed

+373
-0
lines changed

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. Multiple encryption\noption values can be passed to this call in a slice. For more information\nabout how items in the slice are applied to images, and possibly ancestor\nimages refer to the documentation in the C api for rbd_encryption_load2.\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
},

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

rbd/encryption_load2.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 <stdlib.h>
10+
// #include <errno.h>
11+
// #include <rbd/librbd.h>
12+
import "C"
13+
14+
import (
15+
"unsafe"
16+
)
17+
18+
type encryptionOptions2 interface {
19+
EncryptionOptions
20+
writeEncryptionSpec(spec *C.rbd_encryption_spec_t) func()
21+
}
22+
23+
func (opts EncryptionOptionsLUKS1) writeEncryptionSpec(spec *C.rbd_encryption_spec_t) func() {
24+
/* only C memory should be attached to spec */
25+
cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
26+
cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks1_format_options_t)
27+
cOpts := (*C.rbd_encryption_luks1_format_options_t)(C.malloc(cOptsSize))
28+
cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
29+
cOpts.passphrase = cPassphrase
30+
cOpts.passphrase_size = C.size_t(len(opts.Passphrase))
31+
32+
spec.format = C.RBD_ENCRYPTION_FORMAT_LUKS1
33+
spec.opts = C.rbd_encryption_options_t(cOpts)
34+
spec.opts_size = cOptsSize
35+
return func() {
36+
C.free(unsafe.Pointer(cOpts.passphrase))
37+
C.free(unsafe.Pointer(cOpts))
38+
}
39+
}
40+
41+
func (opts EncryptionOptionsLUKS2) writeEncryptionSpec(spec *C.rbd_encryption_spec_t) func() {
42+
/* only C memory should be attached to spec */
43+
cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
44+
cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks2_format_options_t)
45+
cOpts := (*C.rbd_encryption_luks2_format_options_t)(C.malloc(cOptsSize))
46+
cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
47+
cOpts.passphrase = cPassphrase
48+
cOpts.passphrase_size = C.size_t(len(opts.Passphrase))
49+
50+
spec.format = C.RBD_ENCRYPTION_FORMAT_LUKS2
51+
spec.opts = C.rbd_encryption_options_t(cOpts)
52+
spec.opts_size = cOptsSize
53+
return func() {
54+
C.free(unsafe.Pointer(cOpts.passphrase))
55+
C.free(unsafe.Pointer(cOpts))
56+
}
57+
}
58+
59+
// EncryptionLoad2 enables IO on an open encrypted image. Multiple encryption
60+
// option values can be passed to this call in a slice. For more information
61+
// about how items in the slice are applied to images, and possibly ancestor
62+
// images refer to the documentation in the C api for rbd_encryption_load2.
63+
//
64+
// Implements:
65+
//
66+
// int rbd_encryption_load2(rbd_image_t image,
67+
// const rbd_encryption_spec_t *specs,
68+
// size_t spec_count);
69+
func (image *Image) EncryptionLoad2(opts []EncryptionOptions) error {
70+
if image.image == nil {
71+
return ErrImageNotOpen
72+
}
73+
for _, o := range opts {
74+
if _, ok := o.(encryptionOptions2); !ok {
75+
// this should not happen unless someone adds a new type
76+
// implementing EncryptionOptions but fails to add a
77+
// writeEncryptionSpec such that the type is not also implementing
78+
// encryptionOptions2.
79+
return getError(C.EINVAL)
80+
}
81+
}
82+
83+
length := len(opts)
84+
cspecs := (*C.rbd_encryption_spec_t)(C.malloc(
85+
C.size_t(C.sizeof_rbd_encryption_spec_t * length)))
86+
specs := unsafe.Slice(cspecs, length)
87+
freeFuncs := make([]func(), length)
88+
89+
for idx, option := range opts {
90+
f := option.(encryptionOptions2).writeEncryptionSpec(&specs[idx])
91+
freeFuncs[idx] = f
92+
}
93+
defer func() {
94+
for _, f := range freeFuncs {
95+
f()
96+
}
97+
C.free(unsafe.Pointer(cspecs))
98+
}()
99+
100+
ret := C.rbd_encryption_load2(
101+
image.image,
102+
cspecs,
103+
C.size_t(length))
104+
return getError(ret)
105+
}

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)