1*a2cd85f6SMaxim Levitsky#!/usr/bin/env python3 2*a2cd85f6SMaxim Levitsky# 3*a2cd85f6SMaxim Levitsky# Test case for encryption key management versus image sharing 4*a2cd85f6SMaxim Levitsky# 5*a2cd85f6SMaxim Levitsky# Copyright (C) 2019 Red Hat, Inc. 6*a2cd85f6SMaxim Levitsky# 7*a2cd85f6SMaxim Levitsky# This program is free software; you can redistribute it and/or modify 8*a2cd85f6SMaxim Levitsky# it under the terms of the GNU General Public License as published by 9*a2cd85f6SMaxim Levitsky# the Free Software Foundation; either version 2 of the License, or 10*a2cd85f6SMaxim Levitsky# (at your option) any later version. 11*a2cd85f6SMaxim Levitsky# 12*a2cd85f6SMaxim Levitsky# This program is distributed in the hope that it will be useful, 13*a2cd85f6SMaxim Levitsky# but WITHOUT ANY WARRANTY; without even the implied warranty of 14*a2cd85f6SMaxim Levitsky# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15*a2cd85f6SMaxim Levitsky# GNU General Public License for more details. 16*a2cd85f6SMaxim Levitsky# 17*a2cd85f6SMaxim Levitsky# You should have received a copy of the GNU General Public License 18*a2cd85f6SMaxim Levitsky# along with this program. If not, see <http://www.gnu.org/licenses/>. 19*a2cd85f6SMaxim Levitsky# 20*a2cd85f6SMaxim Levitsky 21*a2cd85f6SMaxim Levitskyimport iotests 22*a2cd85f6SMaxim Levitskyimport os 23*a2cd85f6SMaxim Levitskyimport time 24*a2cd85f6SMaxim Levitskyimport json 25*a2cd85f6SMaxim Levitsky 26*a2cd85f6SMaxim Levitskytest_img = os.path.join(iotests.test_dir, 'test.img') 27*a2cd85f6SMaxim Levitsky 28*a2cd85f6SMaxim Levitskyclass Secret: 29*a2cd85f6SMaxim Levitsky def __init__(self, index): 30*a2cd85f6SMaxim Levitsky self._id = "keysec" + str(index) 31*a2cd85f6SMaxim Levitsky # you are not supposed to see the password... 32*a2cd85f6SMaxim Levitsky self._secret = "hunter" + str(index) 33*a2cd85f6SMaxim Levitsky 34*a2cd85f6SMaxim Levitsky def id(self): 35*a2cd85f6SMaxim Levitsky return self._id 36*a2cd85f6SMaxim Levitsky 37*a2cd85f6SMaxim Levitsky def secret(self): 38*a2cd85f6SMaxim Levitsky return self._secret 39*a2cd85f6SMaxim Levitsky 40*a2cd85f6SMaxim Levitsky def to_cmdline_object(self): 41*a2cd85f6SMaxim Levitsky return [ "secret,id=" + self._id + ",data=" + self._secret] 42*a2cd85f6SMaxim Levitsky 43*a2cd85f6SMaxim Levitsky def to_qmp_object(self): 44*a2cd85f6SMaxim Levitsky return { "qom_type" : "secret", "id": self.id(), 45*a2cd85f6SMaxim Levitsky "props": { "data": self.secret() } } 46*a2cd85f6SMaxim Levitsky 47*a2cd85f6SMaxim Levitsky################################################################################ 48*a2cd85f6SMaxim Levitsky 49*a2cd85f6SMaxim Levitskyclass EncryptionSetupTestCase(iotests.QMPTestCase): 50*a2cd85f6SMaxim Levitsky 51*a2cd85f6SMaxim Levitsky # test case startup 52*a2cd85f6SMaxim Levitsky def setUp(self): 53*a2cd85f6SMaxim Levitsky 54*a2cd85f6SMaxim Levitsky # start the VMs 55*a2cd85f6SMaxim Levitsky self.vm1 = iotests.VM(path_suffix = 'VM1') 56*a2cd85f6SMaxim Levitsky self.vm2 = iotests.VM(path_suffix = 'VM2') 57*a2cd85f6SMaxim Levitsky self.vm1.launch() 58*a2cd85f6SMaxim Levitsky self.vm2.launch() 59*a2cd85f6SMaxim Levitsky 60*a2cd85f6SMaxim Levitsky # create the secrets and load 'em into the VMs 61*a2cd85f6SMaxim Levitsky self.secrets = [ Secret(i) for i in range(0, 4) ] 62*a2cd85f6SMaxim Levitsky for secret in self.secrets: 63*a2cd85f6SMaxim Levitsky result = self.vm1.qmp("object-add", **secret.to_qmp_object()) 64*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 65*a2cd85f6SMaxim Levitsky result = self.vm2.qmp("object-add", **secret.to_qmp_object()) 66*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 67*a2cd85f6SMaxim Levitsky 68*a2cd85f6SMaxim Levitsky # test case shutdown 69*a2cd85f6SMaxim Levitsky def tearDown(self): 70*a2cd85f6SMaxim Levitsky # stop the VM 71*a2cd85f6SMaxim Levitsky self.vm1.shutdown() 72*a2cd85f6SMaxim Levitsky self.vm2.shutdown() 73*a2cd85f6SMaxim Levitsky 74*a2cd85f6SMaxim Levitsky ########################################################################### 75*a2cd85f6SMaxim Levitsky # create the encrypted block device using qemu-img 76*a2cd85f6SMaxim Levitsky def createImg(self, file, secret): 77*a2cd85f6SMaxim Levitsky 78*a2cd85f6SMaxim Levitsky output = iotests.qemu_img_pipe( 79*a2cd85f6SMaxim Levitsky 'create', 80*a2cd85f6SMaxim Levitsky '--object', *secret.to_cmdline_object(), 81*a2cd85f6SMaxim Levitsky '-f', iotests.imgfmt, 82*a2cd85f6SMaxim Levitsky '-o', 'key-secret=' + secret.id(), 83*a2cd85f6SMaxim Levitsky '-o', 'iter-time=10', 84*a2cd85f6SMaxim Levitsky file, 85*a2cd85f6SMaxim Levitsky '1M') 86*a2cd85f6SMaxim Levitsky 87*a2cd85f6SMaxim Levitsky iotests.log(output, filters=[iotests.filter_test_dir]) 88*a2cd85f6SMaxim Levitsky 89*a2cd85f6SMaxim Levitsky # attempts to add a key using qemu-img 90*a2cd85f6SMaxim Levitsky def addKey(self, file, secret, new_secret): 91*a2cd85f6SMaxim Levitsky 92*a2cd85f6SMaxim Levitsky image_options = { 93*a2cd85f6SMaxim Levitsky 'key-secret' : secret.id(), 94*a2cd85f6SMaxim Levitsky 'driver' : iotests.imgfmt, 95*a2cd85f6SMaxim Levitsky 'file' : { 96*a2cd85f6SMaxim Levitsky 'driver':'file', 97*a2cd85f6SMaxim Levitsky 'filename': file, 98*a2cd85f6SMaxim Levitsky } 99*a2cd85f6SMaxim Levitsky } 100*a2cd85f6SMaxim Levitsky 101*a2cd85f6SMaxim Levitsky output = iotests.qemu_img_pipe( 102*a2cd85f6SMaxim Levitsky 'amend', 103*a2cd85f6SMaxim Levitsky '--object', *secret.to_cmdline_object(), 104*a2cd85f6SMaxim Levitsky '--object', *new_secret.to_cmdline_object(), 105*a2cd85f6SMaxim Levitsky 106*a2cd85f6SMaxim Levitsky '-o', 'state=active', 107*a2cd85f6SMaxim Levitsky '-o', 'new-secret=' + new_secret.id(), 108*a2cd85f6SMaxim Levitsky '-o', 'iter-time=10', 109*a2cd85f6SMaxim Levitsky 110*a2cd85f6SMaxim Levitsky "json:" + json.dumps(image_options) 111*a2cd85f6SMaxim Levitsky ) 112*a2cd85f6SMaxim Levitsky 113*a2cd85f6SMaxim Levitsky iotests.log(output, filters=[iotests.filter_test_dir]) 114*a2cd85f6SMaxim Levitsky 115*a2cd85f6SMaxim Levitsky ########################################################################### 116*a2cd85f6SMaxim Levitsky # open an encrypted block device 117*a2cd85f6SMaxim Levitsky def openImageQmp(self, vm, id, file, secret, 118*a2cd85f6SMaxim Levitsky readOnly = False, reOpen = False): 119*a2cd85f6SMaxim Levitsky 120*a2cd85f6SMaxim Levitsky command = 'x-blockdev-reopen' if reOpen else 'blockdev-add' 121*a2cd85f6SMaxim Levitsky 122*a2cd85f6SMaxim Levitsky result = vm.qmp(command, ** 123*a2cd85f6SMaxim Levitsky { 124*a2cd85f6SMaxim Levitsky 'driver': iotests.imgfmt, 125*a2cd85f6SMaxim Levitsky 'node-name': id, 126*a2cd85f6SMaxim Levitsky 'read-only': readOnly, 127*a2cd85f6SMaxim Levitsky 'key-secret' : secret.id(), 128*a2cd85f6SMaxim Levitsky 'file': { 129*a2cd85f6SMaxim Levitsky 'driver': 'file', 130*a2cd85f6SMaxim Levitsky 'filename': test_img, 131*a2cd85f6SMaxim Levitsky } 132*a2cd85f6SMaxim Levitsky } 133*a2cd85f6SMaxim Levitsky ) 134*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 135*a2cd85f6SMaxim Levitsky 136*a2cd85f6SMaxim Levitsky # close the encrypted block device 137*a2cd85f6SMaxim Levitsky def closeImageQmp(self, vm, id): 138*a2cd85f6SMaxim Levitsky result = vm.qmp('blockdev-del', **{ 'node-name': id }) 139*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 140*a2cd85f6SMaxim Levitsky 141*a2cd85f6SMaxim Levitsky ########################################################################### 142*a2cd85f6SMaxim Levitsky 143*a2cd85f6SMaxim Levitsky # add a key to an encrypted block device 144*a2cd85f6SMaxim Levitsky def addKeyQmp(self, vm, id, new_secret): 145*a2cd85f6SMaxim Levitsky 146*a2cd85f6SMaxim Levitsky args = { 147*a2cd85f6SMaxim Levitsky 'node-name': id, 148*a2cd85f6SMaxim Levitsky 'job-id' : 'job0', 149*a2cd85f6SMaxim Levitsky 'options' : { 150*a2cd85f6SMaxim Levitsky 'state' : 'active', 151*a2cd85f6SMaxim Levitsky 'driver' : iotests.imgfmt, 152*a2cd85f6SMaxim Levitsky 'new-secret': new_secret.id(), 153*a2cd85f6SMaxim Levitsky 'iter-time' : 10 154*a2cd85f6SMaxim Levitsky }, 155*a2cd85f6SMaxim Levitsky } 156*a2cd85f6SMaxim Levitsky 157*a2cd85f6SMaxim Levitsky result = vm.qmp('x-blockdev-amend', **args) 158*a2cd85f6SMaxim Levitsky assert result['return'] == {} 159*a2cd85f6SMaxim Levitsky vm.run_job('job0') 160*a2cd85f6SMaxim Levitsky 161*a2cd85f6SMaxim Levitsky # test that when the image opened by two qemu processes, 162*a2cd85f6SMaxim Levitsky # neither of them can update the image 163*a2cd85f6SMaxim Levitsky def test1(self): 164*a2cd85f6SMaxim Levitsky self.createImg(test_img, self.secrets[0]); 165*a2cd85f6SMaxim Levitsky 166*a2cd85f6SMaxim Levitsky # VM1 opens the image and adds a key 167*a2cd85f6SMaxim Levitsky self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0]) 168*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[1]) 169*a2cd85f6SMaxim Levitsky 170*a2cd85f6SMaxim Levitsky 171*a2cd85f6SMaxim Levitsky # VM2 opens the image 172*a2cd85f6SMaxim Levitsky self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) 173*a2cd85f6SMaxim Levitsky 174*a2cd85f6SMaxim Levitsky 175*a2cd85f6SMaxim Levitsky # neither VMs now should be able to add a key 176*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 177*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 178*a2cd85f6SMaxim Levitsky 179*a2cd85f6SMaxim Levitsky 180*a2cd85f6SMaxim Levitsky # VM 1 closes the image 181*a2cd85f6SMaxim Levitsky self.closeImageQmp(self.vm1, "testdev") 182*a2cd85f6SMaxim Levitsky 183*a2cd85f6SMaxim Levitsky 184*a2cd85f6SMaxim Levitsky # now VM2 can add the key 185*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 186*a2cd85f6SMaxim Levitsky 187*a2cd85f6SMaxim Levitsky 188*a2cd85f6SMaxim Levitsky # qemu-img should also not be able to add a key 189*a2cd85f6SMaxim Levitsky self.addKey(test_img, self.secrets[0], self.secrets[2]) 190*a2cd85f6SMaxim Levitsky 191*a2cd85f6SMaxim Levitsky # cleanup 192*a2cd85f6SMaxim Levitsky self.closeImageQmp(self.vm2, "testdev") 193*a2cd85f6SMaxim Levitsky os.remove(test_img) 194*a2cd85f6SMaxim Levitsky 195*a2cd85f6SMaxim Levitsky 196*a2cd85f6SMaxim Levitsky def test2(self): 197*a2cd85f6SMaxim Levitsky self.createImg(test_img, self.secrets[0]); 198*a2cd85f6SMaxim Levitsky 199*a2cd85f6SMaxim Levitsky # VM1 opens the image readonly 200*a2cd85f6SMaxim Levitsky self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], 201*a2cd85f6SMaxim Levitsky readOnly = True) 202*a2cd85f6SMaxim Levitsky 203*a2cd85f6SMaxim Levitsky # VM2 opens the image 204*a2cd85f6SMaxim Levitsky self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) 205*a2cd85f6SMaxim Levitsky 206*a2cd85f6SMaxim Levitsky # VM1 can't add a key since image is readonly 207*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 208*a2cd85f6SMaxim Levitsky 209*a2cd85f6SMaxim Levitsky # VM2 can't add a key since VM is has the image opened 210*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm2, "testdev", new_secret = self.secrets[2]) 211*a2cd85f6SMaxim Levitsky 212*a2cd85f6SMaxim Levitsky 213*a2cd85f6SMaxim Levitsky #VM1 reopens the image read-write 214*a2cd85f6SMaxim Levitsky self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], 215*a2cd85f6SMaxim Levitsky reOpen = True, readOnly = False) 216*a2cd85f6SMaxim Levitsky 217*a2cd85f6SMaxim Levitsky # VM1 still can't add the key 218*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 219*a2cd85f6SMaxim Levitsky 220*a2cd85f6SMaxim Levitsky # VM2 gets away 221*a2cd85f6SMaxim Levitsky self.closeImageQmp(self.vm2, "testdev") 222*a2cd85f6SMaxim Levitsky 223*a2cd85f6SMaxim Levitsky # VM1 now can add the key 224*a2cd85f6SMaxim Levitsky self.addKeyQmp(self.vm1, "testdev", new_secret = self.secrets[2]) 225*a2cd85f6SMaxim Levitsky 226*a2cd85f6SMaxim Levitsky self.closeImageQmp(self.vm1, "testdev") 227*a2cd85f6SMaxim Levitsky os.remove(test_img) 228*a2cd85f6SMaxim Levitsky 229*a2cd85f6SMaxim Levitsky 230*a2cd85f6SMaxim Levitskyif __name__ == '__main__': 231*a2cd85f6SMaxim Levitsky # support only raw luks since luks encrypted qcow2 is a proper 232*a2cd85f6SMaxim Levitsky # format driver which doesn't allow any sharing 233*a2cd85f6SMaxim Levitsky iotests.activate_logging() 234*a2cd85f6SMaxim Levitsky iotests.main(supported_fmts = ['luks']) 235