qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

iotests: add tests for blockdev-amend

This commit adds two tests that cover the
new blockdev-amend functionality of luks and qcow2 driver

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
[mreitz: Let 295 verify that LUKS works; drop 295 and 296 from the auto
group]
Signed-off-by: Max Reitz <mreitz@redhat.com>
Message-Id: <20200625125548.870061-20-mreitz@redhat.com>

authored by

Maxim Levitsky and committed by
Max Reitz
a2cd85f6 8ea1613d

+589
+280
tests/qemu-iotests/295
··· 1 + #!/usr/bin/env python3 2 + # 3 + # Test case QMP's encrypted key management 4 + # 5 + # Copyright (C) 2019 Red Hat, Inc. 6 + # 7 + # This program is free software; you can redistribute it and/or modify 8 + # it under the terms of the GNU General Public License as published by 9 + # the Free Software Foundation; either version 2 of the License, or 10 + # (at your option) any later version. 11 + # 12 + # This program is distributed in the hope that it will be useful, 13 + # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + # GNU General Public License for more details. 16 + # 17 + # You should have received a copy of the GNU General Public License 18 + # along with this program. If not, see <http://www.gnu.org/licenses/>. 19 + # 20 + 21 + import iotests 22 + import os 23 + import time 24 + import json 25 + 26 + test_img = os.path.join(iotests.test_dir, 'test.img') 27 + 28 + class Secret: 29 + def __init__(self, index): 30 + self._id = "keysec" + str(index) 31 + # you are not supposed to see the password... 32 + self._secret = "hunter" + str(index) 33 + 34 + def id(self): 35 + return self._id 36 + 37 + def secret(self): 38 + return self._secret 39 + 40 + def to_cmdline_object(self): 41 + return [ "secret,id=" + self._id + ",data=" + self._secret] 42 + 43 + def to_qmp_object(self): 44 + return { "qom_type" : "secret", "id": self.id(), 45 + "props": { "data": self.secret() } } 46 + 47 + ################################################################################ 48 + class EncryptionSetupTestCase(iotests.QMPTestCase): 49 + 50 + # test case startup 51 + def setUp(self): 52 + # start the VM 53 + self.vm = iotests.VM() 54 + self.vm.launch() 55 + 56 + # create the secrets and load 'em into the VM 57 + self.secrets = [ Secret(i) for i in range(0, 6) ] 58 + for secret in self.secrets: 59 + result = self.vm.qmp("object-add", **secret.to_qmp_object()) 60 + self.assert_qmp(result, 'return', {}) 61 + 62 + if iotests.imgfmt == "qcow2": 63 + self.pfx = "encrypt." 64 + self.img_opts = [ '-o', "encrypt.format=luks" ] 65 + else: 66 + self.pfx = "" 67 + self.img_opts = [] 68 + 69 + # test case shutdown 70 + def tearDown(self): 71 + # stop the VM 72 + self.vm.shutdown() 73 + 74 + ########################################################################### 75 + # create the encrypted block device 76 + def createImg(self, file, secret): 77 + 78 + iotests.qemu_img( 79 + 'create', 80 + '--object', *secret.to_cmdline_object(), 81 + '-f', iotests.imgfmt, 82 + '-o', self.pfx + 'key-secret=' + secret.id(), 83 + '-o', self.pfx + 'iter-time=10', 84 + *self.img_opts, 85 + file, 86 + '1M') 87 + 88 + ########################################################################### 89 + # open an encrypted block device 90 + def openImageQmp(self, id, file, secret, read_only = False): 91 + 92 + encrypt_options = { 93 + 'key-secret' : secret.id() 94 + } 95 + 96 + if iotests.imgfmt == "qcow2": 97 + encrypt_options = { 98 + 'encrypt': { 99 + 'format':'luks', 100 + **encrypt_options 101 + } 102 + } 103 + 104 + result = self.vm.qmp('blockdev-add', ** 105 + { 106 + 'driver': iotests.imgfmt, 107 + 'node-name': id, 108 + 'read-only': read_only, 109 + 110 + **encrypt_options, 111 + 112 + 'file': { 113 + 'driver': 'file', 114 + 'filename': test_img, 115 + } 116 + } 117 + ) 118 + self.assert_qmp(result, 'return', {}) 119 + 120 + # close the encrypted block device 121 + def closeImageQmp(self, id): 122 + result = self.vm.qmp('blockdev-del', **{ 'node-name': id }) 123 + self.assert_qmp(result, 'return', {}) 124 + 125 + ########################################################################### 126 + # add a key to an encrypted block device 127 + def addKeyQmp(self, id, new_secret, secret = None, 128 + slot = None, force = False): 129 + 130 + crypt_options = { 131 + 'state' : 'active', 132 + 'new-secret' : new_secret.id(), 133 + 'iter-time' : 10 134 + } 135 + 136 + if slot != None: 137 + crypt_options['keyslot'] = slot 138 + 139 + 140 + if secret != None: 141 + crypt_options['secret'] = secret.id() 142 + 143 + if iotests.imgfmt == "qcow2": 144 + crypt_options['format'] = 'luks' 145 + crypt_options = { 146 + 'encrypt': crypt_options 147 + } 148 + 149 + args = { 150 + 'node-name': id, 151 + 'job-id' : 'job_add_key', 152 + 'options' : { 153 + 'driver' : iotests.imgfmt, 154 + **crypt_options 155 + }, 156 + } 157 + 158 + if force == True: 159 + args['force'] = True 160 + 161 + #TODO: check what jobs return 162 + result = self.vm.qmp('x-blockdev-amend', **args) 163 + assert result['return'] == {} 164 + self.vm.run_job('job_add_key') 165 + 166 + # erase a key from an encrypted block device 167 + def eraseKeyQmp(self, id, old_secret = None, slot = None, force = False): 168 + 169 + crypt_options = { 170 + 'state' : 'inactive', 171 + } 172 + 173 + if slot != None: 174 + crypt_options['keyslot'] = slot 175 + if old_secret != None: 176 + crypt_options['old-secret'] = old_secret.id() 177 + 178 + if iotests.imgfmt == "qcow2": 179 + crypt_options['format'] = 'luks' 180 + crypt_options = { 181 + 'encrypt': crypt_options 182 + } 183 + 184 + args = { 185 + 'node-name': id, 186 + 'job-id' : 'job_erase_key', 187 + 'options' : { 188 + 'driver' : iotests.imgfmt, 189 + **crypt_options 190 + }, 191 + } 192 + 193 + if force == True: 194 + args['force'] = True 195 + 196 + result = self.vm.qmp('x-blockdev-amend', **args) 197 + assert result['return'] == {} 198 + self.vm.run_job('job_erase_key') 199 + 200 + ########################################################################### 201 + # create image, and change its key 202 + def testChangeKey(self): 203 + 204 + # create the image with secret0 and open it 205 + self.createImg(test_img, self.secrets[0]); 206 + self.openImageQmp("testdev", test_img, self.secrets[0]) 207 + 208 + # add key to slot 1 209 + self.addKeyQmp("testdev", new_secret = self.secrets[1]) 210 + 211 + # add key to slot 5 212 + self.addKeyQmp("testdev", new_secret = self.secrets[2], slot=5) 213 + 214 + # erase key from slot 0 215 + self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 216 + 217 + #reopen the image with secret1 218 + self.closeImageQmp("testdev") 219 + self.openImageQmp("testdev", test_img, self.secrets[1]) 220 + 221 + # close and erase the image for good 222 + self.closeImageQmp("testdev") 223 + os.remove(test_img) 224 + 225 + # test that if we erase the old password, 226 + # we can still change the encryption keys using 'old-secret' 227 + def testOldPassword(self): 228 + 229 + # create the image with secret0 and open it 230 + self.createImg(test_img, self.secrets[0]); 231 + self.openImageQmp("testdev", test_img, self.secrets[0]) 232 + 233 + # add key to slot 1 234 + self.addKeyQmp("testdev", new_secret = self.secrets[1]) 235 + 236 + # erase key from slot 0 237 + self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 238 + 239 + # this will fail as the old password is no longer valid 240 + self.addKeyQmp("testdev", new_secret = self.secrets[2]) 241 + 242 + # this will work 243 + self.addKeyQmp("testdev", new_secret = self.secrets[2], secret = self.secrets[1]) 244 + 245 + # close and erase the image for good 246 + self.closeImageQmp("testdev") 247 + os.remove(test_img) 248 + 249 + def testUseForceLuke(self): 250 + 251 + self.createImg(test_img, self.secrets[0]); 252 + self.openImageQmp("testdev", test_img, self.secrets[0]) 253 + 254 + # Add bunch of secrets 255 + self.addKeyQmp("testdev", new_secret = self.secrets[1], slot=4) 256 + self.addKeyQmp("testdev", new_secret = self.secrets[4], slot=2) 257 + 258 + # overwrite an active secret 259 + self.addKeyQmp("testdev", new_secret = self.secrets[5], slot=2) 260 + self.addKeyQmp("testdev", new_secret = self.secrets[5], slot=2, force=True) 261 + 262 + self.addKeyQmp("testdev", new_secret = self.secrets[0]) 263 + 264 + # Now erase all the secrets 265 + self.eraseKeyQmp("testdev", old_secret = self.secrets[5]) 266 + self.eraseKeyQmp("testdev", slot=4) 267 + 268 + # erase last keyslot 269 + self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 270 + self.eraseKeyQmp("testdev", old_secret = self.secrets[0], force=True) 271 + 272 + self.closeImageQmp("testdev") 273 + os.remove(test_img) 274 + 275 + 276 + if __name__ == '__main__': 277 + iotests.verify_working_luks() 278 + # Encrypted formats support 279 + iotests.activate_logging() 280 + iotests.main(supported_fmts = ['qcow2', 'luks'])
+40
tests/qemu-iotests/295.out
··· 1 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 2 + {"return": {}} 3 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 4 + {"return": {}} 5 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 6 + {"return": {}} 7 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 8 + {"return": {}} 9 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 10 + {"return": {}} 11 + Job failed: Invalid password, cannot unlock any keyslot 12 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 13 + {"return": {}} 14 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 15 + {"return": {}} 16 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 17 + {"return": {}} 18 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 19 + {"return": {}} 20 + Job failed: Refusing to overwrite active keyslot 2 - please erase it first 21 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 22 + {"return": {}} 23 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 24 + {"return": {}} 25 + {"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} 26 + {"return": {}} 27 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 28 + {"return": {}} 29 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 30 + {"return": {}} 31 + Job failed: All the active keyslots match the (old) password that was given and erasing them will erase all the data in the image irreversibly - refusing operation 32 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 33 + {"return": {}} 34 + {"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} 35 + {"return": {}} 36 + ... 37 + ---------------------------------------------------------------------- 38 + Ran 3 tests 39 + 40 + OK
+234
tests/qemu-iotests/296
··· 1 + #!/usr/bin/env python3 2 + # 3 + # Test case for encryption key management versus image sharing 4 + # 5 + # Copyright (C) 2019 Red Hat, Inc. 6 + # 7 + # This program is free software; you can redistribute it and/or modify 8 + # it under the terms of the GNU General Public License as published by 9 + # the Free Software Foundation; either version 2 of the License, or 10 + # (at your option) any later version. 11 + # 12 + # This program is distributed in the hope that it will be useful, 13 + # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + # GNU General Public License for more details. 16 + # 17 + # You should have received a copy of the GNU General Public License 18 + # along with this program. If not, see <http://www.gnu.org/licenses/>. 19 + # 20 + 21 + import iotests 22 + import os 23 + import time 24 + import json 25 + 26 + test_img = os.path.join(iotests.test_dir, 'test.img') 27 + 28 + class Secret: 29 + def __init__(self, index): 30 + self._id = "keysec" + str(index) 31 + # you are not supposed to see the password... 32 + self._secret = "hunter" + str(index) 33 + 34 + def id(self): 35 + return self._id 36 + 37 + def secret(self): 38 + return self._secret 39 + 40 + def to_cmdline_object(self): 41 + return [ "secret,id=" + self._id + ",data=" + self._secret] 42 + 43 + def to_qmp_object(self): 44 + return { "qom_type" : "secret", "id": self.id(), 45 + "props": { "data": self.secret() } } 46 + 47 + ################################################################################ 48 + 49 + class EncryptionSetupTestCase(iotests.QMPTestCase): 50 + 51 + # test case startup 52 + def setUp(self): 53 + 54 + # start the VMs 55 + self.vm1 = iotests.VM(path_suffix = 'VM1') 56 + self.vm2 = iotests.VM(path_suffix = 'VM2') 57 + self.vm1.launch() 58 + self.vm2.launch() 59 + 60 + # create the secrets and load 'em into the VMs 61 + self.secrets = [ Secret(i) for i in range(0, 4) ] 62 + for secret in self.secrets: 63 + result = self.vm1.qmp("object-add", **secret.to_qmp_object()) 64 + self.assert_qmp(result, 'return', {}) 65 + result = self.vm2.qmp("object-add", **secret.to_qmp_object()) 66 + self.assert_qmp(result, 'return', {}) 67 + 68 + # test case shutdown 69 + def tearDown(self): 70 + # stop the VM 71 + self.vm1.shutdown() 72 + self.vm2.shutdown() 73 + 74 + ########################################################################### 75 + # create the encrypted block device using qemu-img 76 + def createImg(self, file, secret): 77 + 78 + output = iotests.qemu_img_pipe( 79 + 'create', 80 + '--object', *secret.to_cmdline_object(), 81 + '-f', iotests.imgfmt, 82 + '-o', 'key-secret=' + secret.id(), 83 + '-o', 'iter-time=10', 84 + file, 85 + '1M') 86 + 87 + iotests.log(output, filters=[iotests.filter_test_dir]) 88 + 89 + # attempts to add a key using qemu-img 90 + def addKey(self, file, secret, new_secret): 91 + 92 + image_options = { 93 + 'key-secret' : secret.id(), 94 + 'driver' : iotests.imgfmt, 95 + 'file' : { 96 + 'driver':'file', 97 + 'filename': file, 98 + } 99 + } 100 + 101 + output = iotests.qemu_img_pipe( 102 + 'amend', 103 + '--object', *secret.to_cmdline_object(), 104 + '--object', *new_secret.to_cmdline_object(), 105 + 106 + '-o', 'state=active', 107 + '-o', 'new-secret=' + new_secret.id(), 108 + '-o', 'iter-time=10', 109 + 110 + "json:" + json.dumps(image_options) 111 + ) 112 + 113 + iotests.log(output, filters=[iotests.filter_test_dir]) 114 + 115 + ########################################################################### 116 + # open an encrypted block device 117 + def openImageQmp(self, vm, id, file, secret, 118 + readOnly = False, reOpen = False): 119 + 120 + command = 'x-blockdev-reopen' if reOpen else 'blockdev-add' 121 + 122 + result = vm.qmp(command, ** 123 + { 124 + 'driver': iotests.imgfmt, 125 + 'node-name': id, 126 + 'read-only': readOnly, 127 + 'key-secret' : secret.id(), 128 + 'file': { 129 + 'driver': 'file', 130 + 'filename': test_img, 131 + } 132 + } 133 + ) 134 + self.assert_qmp(result, 'return', {}) 135 + 136 + # close the encrypted block device 137 + def closeImageQmp(self, vm, id): 138 + result = vm.qmp('blockdev-del', **{ 'node-name': id }) 139 + self.assert_qmp(result, 'return', {}) 140 + 141 + ########################################################################### 142 + 143 + # add a key to an encrypted block device 144 + def addKeyQmp(self, vm, id, new_secret): 145 + 146 + args = { 147 + 'node-name': id, 148 + 'job-id' : 'job0', 149 + 'options' : { 150 + 'state' : 'active', 151 + 'driver' : iotests.imgfmt, 152 + 'new-secret': new_secret.id(), 153 + 'iter-time' : 10 154 + }, 155 + } 156 + 157 + result = vm.qmp('x-blockdev-amend', **args) 158 + assert result['return'] == {} 159 + vm.run_job('job0') 160 + 161 + # test that when the image opened by two qemu processes, 162 + # neither of them can update the image 163 + def test1(self): 164 + self.createImg(test_img, self.secrets[0]); 165 + 166 + # VM1 opens the image and adds a key 167 + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0]) 168 + self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[1]) 169 + 170 + 171 + # VM2 opens the image 172 + self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) 173 + 174 + 175 + # neither VMs now should be able to add a key 176 + self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 177 + self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 178 + 179 + 180 + # VM 1 closes the image 181 + self.closeImageQmp(self.vm1, "testdev") 182 + 183 + 184 + # now VM2 can add the key 185 + self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 186 + 187 + 188 + # qemu-img should also not be able to add a key 189 + self.addKey(test_img, self.secrets[0], self.secrets[2]) 190 + 191 + # cleanup 192 + self.closeImageQmp(self.vm2, "testdev") 193 + os.remove(test_img) 194 + 195 + 196 + def test2(self): 197 + self.createImg(test_img, self.secrets[0]); 198 + 199 + # VM1 opens the image readonly 200 + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], 201 + readOnly = True) 202 + 203 + # VM2 opens the image 204 + self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) 205 + 206 + # VM1 can't add a key since image is readonly 207 + self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 208 + 209 + # VM2 can't add a key since VM is has the image opened 210 + self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 211 + 212 + 213 + #VM1 reopens the image read-write 214 + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], 215 + reOpen = True, readOnly = False) 216 + 217 + # VM1 still can't add the key 218 + self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 219 + 220 + # VM2 gets away 221 + self.closeImageQmp(self.vm2, "testdev") 222 + 223 + # VM1 now can add the key 224 + self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 225 + 226 + self.closeImageQmp(self.vm1, "testdev") 227 + os.remove(test_img) 228 + 229 + 230 + if __name__ == '__main__': 231 + # support only raw luks since luks encrypted qcow2 is a proper 232 + # format driver which doesn't allow any sharing 233 + iotests.activate_logging() 234 + iotests.main(supported_fmts = ['luks'])
+33
tests/qemu-iotests/296.out
··· 1 + Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10 2 + 3 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 4 + {"return": {}} 5 + Job failed: Failed to get shared "consistent read" lock 6 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 7 + {"return": {}} 8 + Job failed: Failed to get shared "consistent read" lock 9 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 10 + {"return": {}} 11 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 12 + {"return": {}} 13 + qemu-img: Failed to get shared "consistent read" lock 14 + Is another process using the image [TEST_DIR/test.img]? 15 + 16 + Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10 17 + 18 + Job failed: Block node is read-only 19 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 20 + {"return": {}} 21 + Job failed: Failed to get shared "consistent read" lock 22 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 23 + {"return": {}} 24 + Job failed: Failed to get shared "consistent read" lock 25 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 26 + {"return": {}} 27 + {"execute": "job-dismiss", "arguments": {"id": "job0"}} 28 + {"return": {}} 29 + .. 30 + ---------------------------------------------------------------------- 31 + Ran 2 tests 32 + 33 + OK
+2
tests/qemu-iotests/group
··· 303 303 292 rw auto quick 304 304 293 rw 305 305 294 rw quick 306 + 295 rw 307 + 296 rw 306 308 297 meta