backupcompatibility.py 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public License
  9. # as published by the Free Software Foundation; either version 2.1
  10. # of the License, or (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 Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. import functools
  22. import shutil
  23. import tempfile
  24. import unittest
  25. from distutils import spawn
  26. from multiprocessing import Queue
  27. import os
  28. import subprocess
  29. import logging
  30. import time
  31. import contextlib
  32. try:
  33. import unittest.mock as mock
  34. except ImportError:
  35. import mock
  36. import re
  37. import multiprocessing
  38. import pkg_resources
  39. import sys
  40. import qubesadmin.backup.core2
  41. import qubesadmin.backup.core3
  42. import qubesadmin.exc
  43. import qubesadmin.firewall
  44. import qubesadmin.storage
  45. import qubesadmin.tests
  46. import qubesadmin.tests.backup
  47. MANGLED_SUBDIRS_R2 = {
  48. "test-work": "vm5",
  49. "test-template-clone": "vm9",
  50. "test-custom-template-appvm": "vm10",
  51. "test-standalonevm": "vm11",
  52. "test-testproxy": "vm12",
  53. "test-testhvm": "vm14",
  54. "test-net": "vm16",
  55. }
  56. MANGLED_SUBDIRS_R4 = {
  57. "test-work": "vm3",
  58. "test-fedora-25-clone": "vm7",
  59. "test-custom-template-appvm": "vm31",
  60. "test-standalonevm": "vm4",
  61. "test-proxy": "vm30",
  62. "test-hvm": "vm9",
  63. "test-net": "vm6",
  64. "test-d8test": "vm20",
  65. }
  66. APPTEMPLATE_R2B2 = '''
  67. [Desktop Entry]
  68. Name=%VMNAME%: {name}
  69. GenericName=%VMNAME%: {name}
  70. GenericName[ca]=%VMNAME%: Navegador web
  71. GenericName[cs]=%VMNAME%: Webový prohlížeč
  72. GenericName[es]=%VMNAME%: Navegador web
  73. GenericName[fa]=%VMNAME%: مرورر اینترنتی
  74. GenericName[fi]=%VMNAME%: WWW-selain
  75. GenericName[fr]=%VMNAME%: Navigateur Web
  76. GenericName[hu]=%VMNAME%: Webböngésző
  77. GenericName[it]=%VMNAME%: Browser Web
  78. GenericName[ja]=%VMNAME%: ウェブ・ブラウザ
  79. GenericName[ko]=%VMNAME%: 웹 브라우저
  80. GenericName[nb]=%VMNAME%: Nettleser
  81. GenericName[nl]=%VMNAME%: Webbrowser
  82. GenericName[nn]=%VMNAME%: Nettlesar
  83. GenericName[no]=%VMNAME%: Nettleser
  84. GenericName[pl]=%VMNAME%: Przeglądarka WWW
  85. GenericName[pt]=%VMNAME%: Navegador Web
  86. GenericName[pt_BR]=%VMNAME%: Navegador Web
  87. GenericName[sk]=%VMNAME%: Internetový prehliadač
  88. GenericName[sv]=%VMNAME%: Webbläsare
  89. Comment={comment}
  90. Comment[ca]=Navegueu per el web
  91. Comment[cs]=Prohlížení stránek World Wide Webu
  92. Comment[de]=Im Internet surfen
  93. Comment[es]=Navegue por la web
  94. Comment[fa]=صفحات شبه جهانی اینترنت را مرور نمایید
  95. Comment[fi]=Selaa Internetin WWW-sivuja
  96. Comment[fr]=Navigue sur Internet
  97. Comment[hu]=A világháló böngészése
  98. Comment[it]=Esplora il web
  99. Comment[ja]=ウェブを閲覧します
  100. Comment[ko]=웹을 돌아 다닙니다
  101. Comment[nb]=Surf på nettet
  102. Comment[nl]=Verken het internet
  103. Comment[nn]=Surf på nettet
  104. Comment[no]=Surf på nettet
  105. Comment[pl]=Przeglądanie stron WWW
  106. Comment[pt]=Navegue na Internet
  107. Comment[pt_BR]=Navegue na Internet
  108. Comment[sk]=Prehliadanie internetu
  109. Comment[sv]=Surfa på webben
  110. Exec=qvm-run -q --tray -a %VMNAME% '{command} %u'
  111. Categories=Network;WebBrowser;
  112. X-Qubes-VmName=%VMNAME%
  113. Icon=%VMDIR%/icon.png
  114. '''
  115. QUBESXML_R1 = '''<?xml version='1.0' encoding='UTF-8'?>
  116. <QubesVmCollection clockvm="2" default_fw_netvm="2" default_kernel="3.2.7-10" default_netvm="3" default_template="1" updatevm="3"><QubesTemplateVm conf_file="fedora-17-x64.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/vm-templates/fedora-17-x64" include_in_backups="True" installed_by_rpm="True" internal="False" kernel="3.2.7-10" kernelopts="" label="gray" maxmem="4063" memory="400" name="fedora-17-x64" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="1" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="none" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesNetVm conf_file="netvm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/servicevms/netvm" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="iommu=soft swiotlb=2048" label="red" maxmem="4063" memory="200" name="netvm" netid="1" pcidevs="[&apos;00:19.0&apos;, &apos;03:00.0&apos;]" private_img="private.img" qid="2" root_img="root.img" services="{&apos;ntpd&apos;: False, &apos;meminfo-writer&apos;: False}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" vcpus="2" volatile_img="volatile.img" /><QubesProxyVm conf_file="firewallvm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/servicevms/firewallvm" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="green" maxmem="4063" memory="200" name="firewallvm" netid="2" netvm_qid="2" pcidevs="[]" private_img="private.img" qid="3" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="fedora-17-x64-dvm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/fedora-17-x64-dvm" include_in_backups="True" installed_by_rpm="False" internal="True" kernel="3.2.7-10" kernelopts="" label="gray" maxmem="4063" memory="400" name="fedora-17-x64-dvm" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="4" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="1" volatile_img="volatile.img" /><QubesAppVm conf_file="test-work.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/test-work" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="green" maxmem="4063" memory="400" name="test-work" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="5" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="personal.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/personal" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="yellow" maxmem="4063" memory="400" name="personal" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="6" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="banking.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/banking" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="green" maxmem="4063" memory="400" name="banking" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="7" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="untrusted.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/untrusted" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="red" maxmem="4063" memory="400" name="untrusted" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="8" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="test-standalonevm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/test-standalonevm" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="None" kernelopts="" label="red" maxmem="4063" memory="400" name="test-standalonevm" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="9" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="none" uses_default_kernel="False" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="test-testvm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/test-testvm" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="red" mac="00:16:3E:5E:6C:55" maxmem="4063" memory="400" name="test-testvm" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="10" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesTemplateVm conf_file="test-template-clone.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/vm-templates/test-template-clone" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="gray" maxmem="4063" memory="400" name="test-template-clone" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="11" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="none" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesAppVm conf_file="test-custom-template-appvm.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/appvms/test-custom-template-appvm" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="yellow" maxmem="4063" memory="400" name="test-custom-template-appvm" netvm_qid="3" pcidevs="[]" private_img="private.img" qid="12" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="11" uses_default_kernel="True" uses_default_kernelopts="True" uses_default_netvm="True" vcpus="2" volatile_img="volatile.img" /><QubesProxyVm conf_file="test-testproxy.conf" debug="False" default_user="user" dir_path="/var/lib/qubes/servicevms/test-testproxy" include_in_backups="True" installed_by_rpm="False" internal="False" kernel="3.2.7-10" kernelopts="" label="yellow" maxmem="4063" memory="200" name="test-testproxy" netid="3" netvm_qid="2" pcidevs="[]" private_img="private.img" qid="13" root_img="root.img" services="{&apos;meminfo-writer&apos;: True}" template_qid="1" uses_default_kernel="True" uses_default_kernelopts="True" vcpus="2" volatile_img="volatile.img" /></QubesVmCollection>
  117. '''
  118. BACKUP_HEADER_R2 = '''version=3
  119. hmac-algorithm=SHA512
  120. crypto-algorithm=aes-256-cbc
  121. encrypted={encrypted}
  122. compressed={compressed}
  123. compression-filter=gzip
  124. '''
  125. BACKUP_HEADER_R4 = '''version=4
  126. hmac-algorithm=scrypt
  127. encrypted=True
  128. compressed={compressed}
  129. compression-filter={compression_filter}
  130. backup-id=20161020T123455-1234
  131. '''
  132. parsed_qubes_xml_r2 = {
  133. 'domains': {
  134. 'dom0': {
  135. 'klass': 'AdminVM',
  136. 'label': 'black',
  137. 'properties': {},
  138. 'devices': {},
  139. 'tags': set(),
  140. 'features': {},
  141. 'template': None,
  142. 'backup_path': 'dom0-home/user',
  143. 'included_in_backup': True,
  144. },
  145. 'fedora-20-x64': {
  146. 'klass': 'TemplateVM',
  147. 'label': 'black',
  148. 'properties': {
  149. 'maxmem': '1535',
  150. },
  151. 'devices': {},
  152. 'tags': set(),
  153. 'features': {
  154. 'service.meminfo-writer': True,
  155. 'qrexec': True,
  156. 'gui': True,
  157. },
  158. 'template': None,
  159. 'backup_path': None,
  160. 'included_in_backup': False,
  161. },
  162. 'netvm': {
  163. 'klass': 'AppVM',
  164. 'label': 'red',
  165. 'properties': {
  166. 'maxmem': '1535',
  167. 'memory': '200',
  168. 'netvm': None,
  169. 'default_dispvm': 'disp-no-netvm',
  170. 'provides_network': True},
  171. 'devices': {
  172. 'pci': {
  173. ('dom0', '02_00.0'): {},
  174. ('dom0', '03_00.0'): {},
  175. }
  176. },
  177. 'tags': set(),
  178. 'features': {
  179. 'service.ntpd': False,
  180. 'service.meminfo-writer': False
  181. },
  182. 'template': 'fedora-20-x64',
  183. 'backup_path': None,
  184. 'included_in_backup': False,
  185. },
  186. 'firewallvm': {
  187. 'klass': 'AppVM',
  188. 'label': 'green',
  189. 'properties': {
  190. 'maxmem': '1535',
  191. 'memory': '200',
  192. 'provides_network': True
  193. },
  194. 'devices': {},
  195. 'tags': set(),
  196. 'features': {'service.meminfo-writer': True},
  197. 'template': 'fedora-20-x64',
  198. 'backup_path': None,
  199. 'included_in_backup': False,
  200. },
  201. 'fedora-20-x64-dvm': {
  202. 'klass': 'AppVM',
  203. 'label': 'gray',
  204. 'properties': {
  205. 'maxmem': '1535',
  206. 'vcpus': '1'
  207. },
  208. 'devices': {},
  209. 'tags': set(),
  210. 'features': {
  211. 'internal': True, 'service.meminfo-writer': True},
  212. 'template': 'fedora-20-x64',
  213. 'backup_path': None,
  214. 'included_in_backup': False,
  215. },
  216. 'banking': {
  217. 'klass': 'AppVM',
  218. 'label': 'green',
  219. 'properties': {'maxmem': '1535'},
  220. 'devices': {},
  221. 'tags': set(),
  222. 'features': {'service.meminfo-writer': True},
  223. 'template': 'fedora-20-x64',
  224. 'backup_path': None,
  225. 'included_in_backup': False,
  226. },
  227. 'personal': {
  228. 'klass': 'AppVM',
  229. 'label': 'yellow',
  230. 'properties': {'maxmem': '1535'},
  231. 'devices': {},
  232. 'tags': set(),
  233. 'features': {'service.meminfo-writer': True},
  234. 'template': 'fedora-20-x64',
  235. 'backup_path': None,
  236. 'included_in_backup': False,
  237. },
  238. 'untrusted': {
  239. 'klass': 'AppVM',
  240. 'label': 'red',
  241. 'properties': {
  242. 'maxmem': '1535',
  243. 'netvm': 'test-testproxy',
  244. 'default_dispvm': 'disp-test-testproxy',
  245. },
  246. 'devices': {},
  247. 'tags': set(),
  248. 'features': {'service.meminfo-writer': True},
  249. 'template': 'fedora-20-x64',
  250. 'backup_path': None,
  251. 'included_in_backup': False,
  252. },
  253. 'testproxy2': {
  254. 'klass': 'AppVM',
  255. 'label': 'red',
  256. 'properties': {
  257. 'maxmem': '1535',
  258. 'memory': '200',
  259. 'provides_network': True},
  260. 'devices': {},
  261. 'tags': set(),
  262. 'features': {'service.meminfo-writer': True},
  263. 'template': 'test-template-clone',
  264. 'backup_path': None,
  265. 'included_in_backup': False,
  266. },
  267. 'test-testproxy': {
  268. 'klass': 'AppVM',
  269. 'label': 'red',
  270. 'properties': {
  271. 'maxmem': '1535',
  272. 'memory': '200',
  273. 'provides_network': True},
  274. 'devices': {},
  275. 'tags': set(),
  276. 'features': {'service.meminfo-writer': True},
  277. 'template': 'fedora-20-x64',
  278. 'backup_path': 'servicevms/test-testproxy',
  279. 'included_in_backup': True,
  280. },
  281. 'test-testhvm': {
  282. 'klass': 'StandaloneVM',
  283. 'label': 'purple',
  284. 'properties': {'kernel': '', 'virt_mode': 'hvm', 'memory': '512'},
  285. 'devices': {},
  286. 'tags': set(),
  287. 'features': {
  288. 'service.meminfo-writer': False,
  289. 'linux-stubdom': False},
  290. 'template': None,
  291. 'backup_path': 'appvms/test-testhvm',
  292. 'included_in_backup': True,
  293. 'root_size': 2097664,
  294. },
  295. 'test-work': {
  296. 'klass': 'AppVM',
  297. 'label': 'green',
  298. 'properties': {'maxmem': '1535'},
  299. 'devices': {},
  300. 'tags': set(),
  301. 'features': {'service.meminfo-writer': True},
  302. 'template': 'fedora-20-x64',
  303. 'backup_path': 'appvms/test-work',
  304. 'included_in_backup': True,
  305. },
  306. 'test-template-clone': {
  307. 'klass': 'TemplateVM',
  308. 'label': 'green',
  309. 'properties': {'maxmem': '1535'},
  310. 'devices': {},
  311. 'tags': set(),
  312. 'features': {
  313. 'service.meminfo-writer': True,
  314. 'qrexec': True,
  315. 'gui': True,
  316. },
  317. 'template': None,
  318. 'backup_path': 'vm-templates/test-template-clone',
  319. 'included_in_backup': True,
  320. 'root_size': 209715712,
  321. },
  322. 'test-custom-template-appvm': {
  323. 'klass': 'AppVM',
  324. 'label': 'yellow',
  325. 'properties': {'maxmem': '1535'},
  326. 'devices': {},
  327. 'tags': set(),
  328. 'features': {'service.meminfo-writer': True},
  329. 'template': 'test-template-clone',
  330. 'backup_path': 'appvms/test-custom-template-appvm',
  331. 'included_in_backup': True,
  332. },
  333. 'test-standalonevm': {
  334. 'klass': 'StandaloneVM',
  335. 'label': 'blue',
  336. 'properties': {'maxmem': '1535'},
  337. 'devices': {},
  338. 'tags': set(),
  339. 'features': {
  340. 'service.meminfo-writer': True,
  341. 'qrexec': True,
  342. 'gui': True,
  343. },
  344. 'template': None,
  345. 'backup_path': 'appvms/test-standalonevm',
  346. 'included_in_backup': True,
  347. 'root_size': 2097664,
  348. },
  349. 'test-net': {
  350. 'klass': 'AppVM',
  351. 'label': 'red',
  352. 'properties': {
  353. 'maxmem': '1535',
  354. 'memory': '200',
  355. 'netvm': None,
  356. 'default_dispvm': 'disp-no-netvm',
  357. 'provides_network': True},
  358. 'devices': {
  359. 'pci': {
  360. ('dom0', '02_00.0'): {},
  361. ('dom0', '03_00.0'): {},
  362. }
  363. },
  364. 'tags': set(),
  365. 'features': {
  366. 'service.ntpd': False,
  367. 'service.meminfo-writer': False
  368. },
  369. 'template': 'fedora-20-x64',
  370. 'backup_path': 'servicevms/test-net',
  371. 'included_in_backup': True,
  372. },
  373. 'disp-no-netvm': {
  374. 'klass': 'AppVM',
  375. 'label': 'red',
  376. 'properties': {
  377. 'netvm': None,
  378. 'template_for_dispvms': True,
  379. },
  380. 'devices': {},
  381. 'features': {},
  382. 'tags': set(),
  383. 'template': None, # default
  384. 'backup_path': None,
  385. 'included_in_backup': True,
  386. },
  387. 'disp-test-testproxy': {
  388. 'klass': 'AppVM',
  389. 'label': 'red',
  390. 'properties': {
  391. 'netvm': 'test-testproxy',
  392. 'template_for_dispvms': True,
  393. },
  394. 'devices': {},
  395. 'features': {},
  396. 'tags': set(),
  397. 'template': None, # default
  398. 'backup_path': None,
  399. 'included_in_backup': True,
  400. },
  401. },
  402. 'globals': {
  403. 'default_template': 'fedora-20-x64',
  404. 'default_kernel': '3.7.6-2',
  405. 'default_netvm': 'firewallvm',
  406. 'clockvm': 'netvm',
  407. 'updatevm': 'firewallvm'
  408. },
  409. }
  410. parsed_qubes_xml_v4 = {
  411. 'domains': {
  412. 'dom0': {
  413. 'klass': 'AdminVM',
  414. 'label': 'black',
  415. 'properties': {},
  416. 'devices': {},
  417. 'tags': set(),
  418. 'features': {},
  419. 'template': None,
  420. 'backup_path': 'dom0-home/user',
  421. 'included_in_backup': True,
  422. },
  423. 'fedora-25': {
  424. 'klass': 'TemplateVM',
  425. 'label': 'black',
  426. 'properties': {},
  427. 'devices': {},
  428. 'tags': {'created-by-test-work'},
  429. 'features': {
  430. 'gui': '1',
  431. 'qrexec': 'True',
  432. 'updates-available': False
  433. },
  434. 'template': None,
  435. 'backup_path': None,
  436. 'included_in_backup': False,
  437. },
  438. 'fedora-25-lvm': {
  439. 'klass': 'TemplateVM',
  440. 'label': 'black',
  441. 'properties': {
  442. 'maxmem': '4000',
  443. },
  444. 'devices': {},
  445. 'tags': set(),
  446. 'features': {},
  447. 'template': None,
  448. 'backup_path': None,
  449. 'included_in_backup': False,
  450. },
  451. 'debian-8': {
  452. 'klass': 'TemplateVM',
  453. 'label': 'black',
  454. 'properties': {},
  455. 'devices': {},
  456. 'tags': {'created-by-dom0'},
  457. 'features': {
  458. 'gui': '1',
  459. 'qrexec': 'True',
  460. 'updates-available': False},
  461. 'template': None,
  462. 'backup_path': None,
  463. 'included_in_backup': False,
  464. },
  465. 'sys-net': {
  466. 'klass': 'AppVM',
  467. 'label': 'red',
  468. 'properties': {
  469. 'virt_mode': 'hvm',
  470. 'kernelopts': 'nopat i8042.nokbd i8042.noaux',
  471. 'maxmem': '300',
  472. 'memory': '300',
  473. 'netvm': None,
  474. 'default_user': 'user',
  475. 'provides_network': 'True'},
  476. 'devices': {
  477. 'pci': {
  478. ('dom0', '02_00.0'): {},
  479. }
  480. },
  481. 'tags': set(),
  482. 'features': {
  483. 'service.clocksync': '1',
  484. 'service.meminfo-writer': False
  485. },
  486. 'template': 'fedora-25',
  487. 'backup_path': None,
  488. 'included_in_backup': False,
  489. },
  490. 'sys-firewall': {
  491. 'klass': 'AppVM',
  492. 'label': 'green',
  493. 'properties': {
  494. 'autostart': 'True',
  495. 'memory': '500',
  496. 'provides_network': 'True'
  497. },
  498. 'devices': {},
  499. 'tags': set(),
  500. 'features': {},
  501. 'template': 'fedora-25',
  502. 'backup_path': None,
  503. 'included_in_backup': False,
  504. },
  505. 'test-d8test': {
  506. 'klass': 'AppVM',
  507. 'label': 'gray',
  508. 'properties': {'debug': 'True', 'kernel': None},
  509. 'devices': {},
  510. 'tags': {'created-by-dom0'},
  511. 'features': {},
  512. 'template': 'debian-8',
  513. 'backup_path': 'appvms/test-d8test',
  514. 'included_in_backup': True,
  515. },
  516. 'fedora-25-dvm': {
  517. 'klass': 'AppVM',
  518. 'label': 'red',
  519. 'properties': {
  520. 'template_for_dispvms': 'True',
  521. 'vcpus': '1',
  522. },
  523. 'devices': {},
  524. 'tags': set(),
  525. 'features': {
  526. 'internal': '1', 'service.meminfo-writer': '1'},
  527. 'template': 'fedora-25',
  528. 'backup_path': None,
  529. 'included_in_backup': False,
  530. },
  531. 'fedora-25-clone-dvm': {
  532. 'klass': 'AppVM',
  533. 'label': 'red',
  534. 'properties': {
  535. 'vcpus': '1',
  536. 'template_for_dispvms': 'True',
  537. },
  538. 'devices': {},
  539. 'tags': set(),
  540. 'features': {
  541. 'internal': '1', 'service.meminfo-writer': '1'},
  542. 'template': 'test-fedora-25-clone',
  543. 'backup_path': None,
  544. 'included_in_backup': False,
  545. },
  546. 'vault': {
  547. 'klass': 'AppVM',
  548. 'label': 'black',
  549. 'properties': {'virt_mode': 'pv', 'maxmem': '1536', 'netvm': None},
  550. 'devices': {},
  551. 'tags': set(),
  552. 'features': {},
  553. 'template': 'fedora-25',
  554. 'backup_path': None,
  555. 'included_in_backup': False,
  556. },
  557. 'personal': {
  558. 'klass': 'AppVM',
  559. 'label': 'yellow',
  560. 'properties': {'netvm': 'sys-firewall'},
  561. 'devices': {},
  562. 'tags': set(),
  563. 'features': {
  564. 'feat1': '1',
  565. 'feat2': False,
  566. 'feat32': '1',
  567. 'featdis': False,
  568. 'xxx': '1'
  569. },
  570. 'template': 'fedora-25',
  571. 'backup_path': None,
  572. 'included_in_backup': False,
  573. },
  574. 'untrusted': {
  575. 'klass': 'AppVM',
  576. 'label': 'red',
  577. 'properties': {
  578. 'netvm': None,
  579. 'backup_timestamp': '1474318497',
  580. 'default_dispvm': 'fedora-25-clone-dvm',
  581. },
  582. 'devices': {},
  583. 'tags': set(),
  584. 'features': {'service.meminfo-writer': '1'},
  585. 'template': 'fedora-25',
  586. 'backup_path': None,
  587. 'included_in_backup': False,
  588. },
  589. 'sys-usb': {
  590. 'klass': 'AppVM',
  591. 'label': 'red',
  592. 'properties': {
  593. 'autostart': 'True',
  594. 'maxmem': '400',
  595. 'provides_network': 'True',
  596. },
  597. 'devices': {},
  598. 'tags': set(),
  599. 'features': {
  600. 'service.meminfo-writer': False,
  601. 'service.network-manager': False,
  602. },
  603. 'template': 'fedora-25',
  604. 'backup_path': None,
  605. 'included_in_backup': False,
  606. },
  607. 'test-proxy': {
  608. 'klass': 'AppVM',
  609. 'label': 'red',
  610. 'properties': {'netvm': 'sys-net', 'provides_network': 'True'},
  611. 'devices': {},
  612. 'tags': {'created-by-dom0'},
  613. 'features': {},
  614. 'template': 'debian-8',
  615. 'backup_path': 'appvms/test-proxy',
  616. 'included_in_backup': True,
  617. },
  618. 'test-hvm': {
  619. 'klass': 'StandaloneVM',
  620. 'label': 'purple',
  621. 'properties': {
  622. 'kernel': None,
  623. 'virt_mode': 'hvm',
  624. 'maxmem': '4000'},
  625. 'devices': {},
  626. 'tags': set(),
  627. 'features': {'service.meminfo-writer': False},
  628. 'template': None,
  629. 'backup_path': 'appvms/test-hvm',
  630. 'included_in_backup': True,
  631. 'root_size': 2097664,
  632. },
  633. 'test-work': {
  634. 'klass': 'AppVM',
  635. 'label': 'green',
  636. 'properties': {
  637. 'ip': '192.168.0.1',
  638. 'maxmem': '4000',
  639. 'memory': '400'},
  640. 'devices': {},
  641. 'tags': {'tag1', 'tag2'},
  642. 'features': {'service.meminfo-writer': '1'},
  643. 'template': 'fedora-25',
  644. 'backup_path': 'appvms/test-work',
  645. 'included_in_backup': True,
  646. },
  647. 'test-fedora-25-clone': {
  648. 'klass': 'TemplateVM',
  649. 'label': 'black',
  650. 'properties': {'maxmem': '4000'},
  651. 'devices': {},
  652. 'tags': set(),
  653. 'features': {'service.meminfo-writer': '1'},
  654. 'template': None,
  655. 'backup_path': 'vm-templates/test-fedora-25-clone',
  656. 'included_in_backup': True,
  657. },
  658. 'test-custom-template-appvm': {
  659. 'klass': 'AppVM',
  660. 'label': 'yellow',
  661. 'properties': {'debug': 'True', 'kernel': None},
  662. 'devices': {},
  663. 'tags': {'created-by-dom0'},
  664. 'features': {},
  665. 'template': 'test-fedora-25-clone',
  666. 'backup_path': 'appvms/test-custom-template-appvm',
  667. 'included_in_backup': True,
  668. },
  669. 'test-standalonevm': {
  670. 'klass': 'StandaloneVM',
  671. 'label': 'blue',
  672. 'properties': {'maxmem': '4000'},
  673. 'devices': {},
  674. 'tags': set(),
  675. 'features': {},
  676. 'template': None,
  677. 'backup_path': 'appvms/test-standalonevm',
  678. 'included_in_backup': True,
  679. 'root_size': 2097664,
  680. },
  681. 'test-net': {
  682. 'klass': 'AppVM',
  683. 'label': 'red',
  684. 'properties': {
  685. 'maxmem': '300',
  686. 'memory': '300',
  687. 'netvm': None,
  688. 'provides_network': 'True'
  689. },
  690. 'devices': {
  691. 'pci': {
  692. ('dom0', '03_00.0'): {},
  693. }
  694. },
  695. 'tags': set(),
  696. 'features': {
  697. 'service.ntpd': False,
  698. 'service.meminfo-writer': False
  699. },
  700. 'template': 'fedora-25',
  701. 'backup_path': 'appvms/test-net',
  702. 'included_in_backup': True,
  703. },
  704. },
  705. 'globals': {
  706. 'default_template': 'fedora-25',
  707. 'default_kernel': '4.9.31-17',
  708. 'default_netvm': 'sys-firewall',
  709. 'default_dispvm': 'fedora-25-dvm',
  710. #'default_fw_netvm': 'sys-net',
  711. 'clockvm': 'sys-net',
  712. 'updatevm': 'sys-firewall'
  713. },
  714. }
  715. class TC_00_QubesXML(qubesadmin.tests.QubesTestCase):
  716. def assertCorrectlyConverted(self, backup_app, expected_data):
  717. self.assertCountEqual(backup_app.domains.keys(),
  718. expected_data['domains'].keys())
  719. for vm in expected_data['domains']:
  720. self.assertEqual(backup_app.domains[vm].name, vm)
  721. self.assertEqual(backup_app.domains[vm].properties,
  722. expected_data['domains'][vm]['properties'], vm)
  723. for devtype in expected_data['domains'][vm]['devices']:
  724. self.assertEqual(backup_app.domains[vm].devices[devtype],
  725. expected_data['domains'][vm]['devices'][devtype], vm)
  726. self.assertEqual(backup_app.domains[vm].tags,
  727. expected_data['domains'][vm]['tags'], vm)
  728. self.assertEqual(backup_app.domains[vm].features,
  729. expected_data['domains'][vm]['features'], vm)
  730. self.assertEqual(backup_app.domains[vm].template,
  731. expected_data['domains'][vm]['template'], vm)
  732. self.assertEqual(backup_app.domains[vm].klass,
  733. expected_data['domains'][vm]['klass'], vm)
  734. self.assertEqual(backup_app.domains[vm].label,
  735. expected_data['domains'][vm]['label'], vm)
  736. self.assertEqual(backup_app.domains[vm].backup_path,
  737. expected_data['domains'][vm]['backup_path'], vm)
  738. self.assertEqual(backup_app.domains[vm].included_in_backup,
  739. expected_data['domains'][vm]['included_in_backup'], vm)
  740. self.assertEqual(backup_app.globals, expected_data['globals'])
  741. def test_000_qubes_xml_r2(self):
  742. xml_data = pkg_resources.resource_string(__name__, 'v3-qubes.xml')
  743. with tempfile.NamedTemporaryFile() as qubes_xml:
  744. qubes_xml.file.write(xml_data)
  745. backup_app = qubesadmin.backup.core2.Core2Qubes(qubes_xml.name)
  746. self.assertCorrectlyConverted(backup_app, parsed_qubes_xml_r2)
  747. def test_010_qubes_xml_r4(self):
  748. self.maxDiff = None
  749. xml_data = pkg_resources.resource_string(__name__, 'v4-qubes.xml')
  750. with tempfile.NamedTemporaryFile() as qubes_xml:
  751. qubes_xml.file.write(xml_data)
  752. backup_app = qubesadmin.backup.core3.Core3Qubes(qubes_xml.name)
  753. self.assertCorrectlyConverted(backup_app, parsed_qubes_xml_v4)
  754. # backup code use multiprocessing, synchronize with main process
  755. class AppProxy(object):
  756. def __init__(self, app, sync_queue):
  757. self._app = app
  758. self._sync_queue = sync_queue
  759. def qubesd_call(self, dest, method, arg=None, payload=None,
  760. payload_stream=None):
  761. if payload_stream:
  762. signature_bin = payload_stream.read(512)
  763. payload = signature_bin.split(b'\0', 1)[0]
  764. subprocess.call(['cat'], stdin=payload_stream,
  765. stdout=subprocess.DEVNULL)
  766. payload_stream.close()
  767. self._sync_queue.put((dest, method, arg, payload))
  768. return self._app.qubesd_call(dest, method, arg, payload)
  769. class MockVolume(qubesadmin.storage.Volume):
  770. def __init__(self, import_data_queue, *args, **kwargs):
  771. super(MockVolume, self).__init__(*args, **kwargs)
  772. self.app = AppProxy(self.app, import_data_queue)
  773. class MockFirewall(qubesadmin.firewall.Firewall):
  774. def __init__(self, import_data_queue, *args, **kwargs):
  775. super(MockFirewall, self).__init__(*args, **kwargs)
  776. self.vm.app = AppProxy(self.vm.app, import_data_queue)
  777. @unittest.skipUnless(os.environ.get('ENABLE_SLOW_TESTS', False),
  778. 'Set ENABLE_SLOW_TESTS=1 environment variable')
  779. class TC_10_BackupCompatibility(qubesadmin.tests.backup.BackupTestCase):
  780. storage_pool = None
  781. def tearDown(self):
  782. super(TC_10_BackupCompatibility, self).tearDown()
  783. def create_whitelisted_appmenus(self, filename):
  784. with open(filename, "w") as f:
  785. f.write("gnome-terminal.desktop\n")
  786. f.write("nautilus.desktop\n")
  787. f.write("firefox.desktop\n")
  788. f.write("mozilla-thunderbird.desktop\n")
  789. f.write("libreoffice-startcenter.desktop\n")
  790. def create_appmenus(self, dir, template, list):
  791. for name in list:
  792. with open(os.path.join(dir, name + ".desktop"), "w") as f:
  793. f.write(template.format(name=name, comment=name, command=name))
  794. def create_private_img(self, filename):
  795. signature = '/'.join(os.path.splitext(filename)[0].split('/')[-2:])
  796. self.create_sparse(filename, 2*2**20, signature=signature.encode())
  797. #subprocess.check_call(["/usr/sbin/mkfs.ext4", "-q", "-F", filename])
  798. def create_volatile_img(self, filename):
  799. self.create_sparse(filename, 11.5*2**20)
  800. # here used to be sfdisk call with "0,1024,S\n,10240,L\n" input,
  801. # but since sfdisk folks like to change command arguments in
  802. # incompatible way, have an partition table verbatim here
  803. ptable = (
  804. '\x00\x00\x00\x00\x00\x00\x00\x00\xab\x39\xd5\xd4\x00\x00\x20\x00'
  805. '\x00\x21\xaa\x82\x82\x28\x08\x00\x00\x00\x00\x00\x00\x20\xaa\x00'
  806. '\x82\x29\x15\x83\x9c\x79\x08\x00\x00\x20\x00\x00\x01\x40\x00\x00'
  807. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
  808. '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\x55'
  809. )
  810. with open(filename, 'r+') as f:
  811. f.seek(0x1b0)
  812. f.write(ptable)
  813. # TODO: mkswap
  814. def fullpath(self, name):
  815. return os.path.join(self.backupdir, name)
  816. def create_v1_files(self, r2b2=False):
  817. appmenus_list = [
  818. "firefox", "gnome-terminal", "evince", "evolution",
  819. "mozilla-thunderbird", "libreoffice-startcenter", "nautilus",
  820. "gedit", "gpk-update-viewer", "gpk-application"
  821. ]
  822. os.mkdir(self.fullpath("appvms"))
  823. os.mkdir(self.fullpath("servicevms"))
  824. os.mkdir(self.fullpath("vm-templates"))
  825. # normal AppVM, with firewall
  826. os.mkdir(self.fullpath("appvms/test-work"))
  827. self.create_whitelisted_appmenus(self.fullpath(
  828. "appvms/test-work/whitelisted-appmenus.list"))
  829. os.symlink("/usr/share/qubes/icons/green.png",
  830. self.fullpath("appvms/test-work/icon.png"))
  831. self.create_private_img(self.fullpath("appvms/test-work/private.img"))
  832. with open(self.fullpath("appvms/test-work/firewall.xml"), "wb") as \
  833. f_firewall:
  834. f_firewall.write(
  835. pkg_resources.resource_string(__name__, 'v3-firewall.xml'))
  836. # StandaloneVM
  837. os.mkdir(self.fullpath("appvms/test-standalonevm"))
  838. self.create_whitelisted_appmenus(self.fullpath(
  839. "appvms/test-standalonevm/whitelisted-appmenus.list"))
  840. os.symlink("/usr/share/qubes/icons/blue.png",
  841. self.fullpath("appvms/test-standalonevm/icon.png"))
  842. self.create_private_img(self.fullpath(
  843. "appvms/test-standalonevm/private.img"))
  844. self.create_sparse(
  845. self.fullpath("appvms/test-standalonevm/root.img"), 10*2**30)
  846. self.fill_image(self.fullpath("appvms/test-standalonevm/root.img"),
  847. 1024*1024, True,
  848. signature=b'test-standalonevm/root')
  849. os.mkdir(self.fullpath("appvms/test-standalonevm/apps.templates"))
  850. self.create_appmenus(self.fullpath("appvms/test-standalonevm/apps"
  851. ".templates"),
  852. APPTEMPLATE_R2B2,
  853. appmenus_list)
  854. os.mkdir(self.fullpath("appvms/test-standalonevm/kernels"))
  855. for k_file in ["initramfs", "vmlinuz", "modules.img"]:
  856. self.fill_image(self.fullpath("appvms/test-standalonevm/kernels/"
  857. + k_file), 1024*1024)
  858. # VM based on custom template
  859. subprocess.check_call(
  860. ["/bin/cp", "-a", self.fullpath("appvms/test-work"),
  861. self.fullpath("appvms/test-custom-template-appvm")])
  862. # override for correct signature
  863. self.create_private_img(
  864. self.fullpath("appvms/test-custom-template-appvm/private.img"))
  865. # HVM
  866. if r2b2:
  867. subprocess.check_call(
  868. ["/bin/cp", "-a", self.fullpath("appvms/test-standalonevm"),
  869. self.fullpath("appvms/test-testhvm")])
  870. # override for correct signature
  871. self.create_private_img(
  872. self.fullpath("appvms/test-testhvm/private.img"))
  873. self.fill_image(self.fullpath("appvms/test-testhvm/root.img"),
  874. 1024*1024, True,
  875. signature=b'test-testhvm/root')
  876. # ProxyVM
  877. os.mkdir(self.fullpath("servicevms/test-testproxy"))
  878. self.create_whitelisted_appmenus(self.fullpath(
  879. "servicevms/test-testproxy/whitelisted-appmenus.list"))
  880. self.create_private_img(
  881. self.fullpath("servicevms/test-testproxy/private.img"))
  882. # NetVM
  883. os.mkdir(self.fullpath("servicevms/test-net"))
  884. self.create_whitelisted_appmenus(self.fullpath(
  885. "servicevms/test-net/whitelisted-appmenus.list"))
  886. self.create_private_img(
  887. self.fullpath("servicevms/test-net/private.img"))
  888. # Custom template
  889. os.mkdir(self.fullpath("vm-templates/test-template-clone"))
  890. self.create_private_img(
  891. self.fullpath("vm-templates/test-template-clone/private.img"))
  892. self.create_sparse(self.fullpath(
  893. "vm-templates/test-template-clone/root-cow.img"), 10*2**20)
  894. self.create_sparse(self.fullpath(
  895. "vm-templates/test-template-clone/root.img"), 10*2**20)
  896. self.fill_image(self.fullpath(
  897. "vm-templates/test-template-clone/root.img"), 100*2**20, True,
  898. signature=b'test-template-clone/root')
  899. self.create_volatile_img(self.fullpath(
  900. "vm-templates/test-template-clone/volatile.img"))
  901. subprocess.check_call([
  902. "/bin/tar", "cS",
  903. "-f", self.fullpath(
  904. "vm-templates/test-template-clone/clean-volatile.img.tar"),
  905. "-C", self.fullpath("vm-templates/test-template-clone"),
  906. "volatile.img"])
  907. self.create_whitelisted_appmenus(self.fullpath(
  908. "vm-templates/test-template-clone/whitelisted-appmenus.list"))
  909. self.create_whitelisted_appmenus(self.fullpath(
  910. "vm-templates/test-template-clone/vm-whitelisted-appmenus.list"))
  911. if r2b2:
  912. self.create_whitelisted_appmenus(self.fullpath(
  913. "vm-templates/test-template-clone/netvm-whitelisted-appmenus"
  914. ".list"))
  915. os.symlink("/usr/share/qubes/icons/green.png",
  916. self.fullpath("vm-templates/test-template-clone/icon.png"))
  917. os.mkdir(
  918. self.fullpath("vm-templates/test-template-clone/apps.templates"))
  919. self.create_appmenus(
  920. self.fullpath("vm-templates/test-template-clone/apps.templates"),
  921. APPTEMPLATE_R2B2,
  922. appmenus_list)
  923. os.mkdir(self.fullpath("vm-templates/test-template-clone/apps"))
  924. self.create_appmenus(
  925. self.fullpath("vm-templates/test-template-clone/apps"),
  926. APPTEMPLATE_R2B2.replace("%VMNAME%", "test-template-clone")
  927. .replace("%VMDIR%", self.fullpath(
  928. "vm-templates/test-template-clone")),
  929. appmenus_list)
  930. self.create_dom0_files()
  931. dom0_dirs = ('Downloads', 'Pictures', 'Documents', '.config', '.local')
  932. dom0_files = ('.bash_history', 'some-file.txt',
  933. 'Pictures/another-file.png')
  934. def create_dom0_files(self):
  935. # dom0 files
  936. os.mkdir(self.fullpath('dom0-home'))
  937. os.mkdir(self.fullpath('dom0-home/user'))
  938. for d in self.dom0_dirs:
  939. os.mkdir(self.fullpath('dom0-home/user/' + d))
  940. for f in self.dom0_files:
  941. with open(self.fullpath('dom0-home/user/' + f), 'w') as ff:
  942. ff.write('some content')
  943. def assertDirectoryExists(self, path):
  944. if not os.path.exists(path):
  945. self.fail(path + ' missing')
  946. if not os.path.isdir(path):
  947. self.fail(path + ' is not a directory')
  948. def assertFileExists(self, path):
  949. if not os.path.exists(path):
  950. self.fail(path + ' missing')
  951. if not os.path.isfile(path):
  952. self.fail(path + ' is not a file')
  953. def assertDom0Restored(self, timestamp):
  954. expected_dir = os.path.expanduser(
  955. '~/home-restore-' + timestamp + '/dom0-home/user')
  956. self.assertTrue(os.path.exists(expected_dir))
  957. for d in self.dom0_dirs:
  958. self.assertDirectoryExists(os.path.join(expected_dir, d))
  959. for f in self.dom0_files:
  960. self.assertFileExists(os.path.join(expected_dir, f))
  961. # cleanup
  962. shutil.rmtree(expected_dir)
  963. def create_v4_files(self):
  964. appmenus_list = [
  965. "firefox", "gnome-terminal", "evince", "evolution",
  966. "mozilla-thunderbird", "libreoffice-startcenter", "nautilus",
  967. "gedit", "gpk-update-viewer", "gpk-application"
  968. ]
  969. os.mkdir(self.fullpath("appvms"))
  970. os.mkdir(self.fullpath("vm-templates"))
  971. # normal AppVMs
  972. for vm in ('test-work', 'test-d8test', 'test-proxy',
  973. 'test-custom-template-appvm', 'test-net'):
  974. os.mkdir(self.fullpath('appvms/{}'.format(vm)))
  975. self.create_whitelisted_appmenus(self.fullpath(
  976. 'appvms/{}/whitelisted-appmenus.list'.format(vm)))
  977. self.create_private_img(self.fullpath('appvms/{}/private.img'.format(
  978. vm)))
  979. # setup firewall only on one VM
  980. with open(self.fullpath("appvms/test-work/firewall.xml"), "wb") as \
  981. f_firewall:
  982. f_firewall.write(
  983. pkg_resources.resource_string(__name__, 'v4-firewall.xml'))
  984. # StandaloneVMs
  985. for vm in ('test-standalonevm', 'test-hvm'):
  986. os.mkdir(self.fullpath('appvms/{}'.format(vm)))
  987. self.create_whitelisted_appmenus(self.fullpath(
  988. 'appvms/{}/whitelisted-appmenus.list'.format(vm)))
  989. self.create_private_img(self.fullpath(
  990. 'appvms/{}/private.img'.format(vm)))
  991. self.create_sparse(
  992. self.fullpath('appvms/{}/root.img'.format(vm)), 10*2**30)
  993. self.fill_image(self.fullpath('appvms/{}/root.img'.format(vm)),
  994. 1024*1024, True,
  995. signature='{}/root'.format(vm).encode())
  996. # only for Linux one
  997. os.mkdir(self.fullpath('appvms/test-standalonevm/apps.templates'))
  998. self.create_appmenus(
  999. self.fullpath('appvms/test-standalonevm/apps.templates'),
  1000. APPTEMPLATE_R2B2,
  1001. appmenus_list)
  1002. # Custom template
  1003. os.mkdir(self.fullpath("vm-templates/test-fedora-25-clone"))
  1004. self.create_private_img(
  1005. self.fullpath("vm-templates/test-fedora-25-clone/private.img"))
  1006. self.create_sparse(self.fullpath(
  1007. "vm-templates/test-fedora-25-clone/root.img"), 10*2**20)
  1008. self.fill_image(self.fullpath(
  1009. "vm-templates/test-fedora-25-clone/root.img"), 1*2**20, True,
  1010. signature=b'test-fedora-25-clone/root')
  1011. self.create_volatile_img(self.fullpath(
  1012. "vm-templates/test-fedora-25-clone/volatile.img"))
  1013. self.create_whitelisted_appmenus(self.fullpath(
  1014. "vm-templates/test-fedora-25-clone/whitelisted-appmenus.list"))
  1015. self.create_whitelisted_appmenus(self.fullpath(
  1016. "vm-templates/test-fedora-25-clone/vm-whitelisted-appmenus.list"))
  1017. os.mkdir(
  1018. self.fullpath("vm-templates/test-fedora-25-clone/apps.templates"))
  1019. self.create_appmenus(
  1020. self.fullpath("vm-templates/test-fedora-25-clone/apps.templates"),
  1021. APPTEMPLATE_R2B2,
  1022. appmenus_list)
  1023. os.mkdir(self.fullpath("vm-templates/test-fedora-25-clone/apps"))
  1024. self.create_appmenus(
  1025. self.fullpath("vm-templates/test-fedora-25-clone/apps"),
  1026. APPTEMPLATE_R2B2.replace("%VMNAME%", "test-fedora-25-clone")
  1027. .replace("%VMDIR%", self.fullpath(
  1028. "vm-templates/test-fedora-25-clone")),
  1029. appmenus_list)
  1030. self.create_dom0_files()
  1031. def scrypt_encrypt(self, f_name, output_name=None, password='qubes',
  1032. basedir=None):
  1033. if basedir is None:
  1034. basedir = self.backupdir
  1035. if output_name is None:
  1036. output_name = f_name + '.enc'
  1037. if f_name == 'backup-header':
  1038. scrypt_pass = 'backup-header!' + password
  1039. else:
  1040. scrypt_pass = '20161020T123455-1234!{}!{}'.format(f_name, password)
  1041. p = subprocess.Popen(['scrypt', 'enc', '-P', '-t', '0.1',
  1042. os.path.join(basedir, f_name), os.path.join(basedir, output_name)],
  1043. stdin=subprocess.PIPE)
  1044. p.communicate(scrypt_pass.encode())
  1045. assert p.wait() == 0
  1046. return output_name
  1047. def calculate_hmac(self, f_name, algorithm="sha512", password="qubes"):
  1048. with open(self.fullpath(f_name), "r") as f_data:
  1049. with open(self.fullpath(f_name+".hmac"), "w") as f_hmac:
  1050. subprocess.check_call(
  1051. ["openssl", "dgst", "-"+algorithm, "-hmac", password],
  1052. stdin=f_data, stdout=f_hmac)
  1053. def append_backup_stream(self, f_name, stream, basedir=None):
  1054. if not basedir:
  1055. basedir = self.backupdir
  1056. subprocess.check_call(["tar", "-cO", "--posix", "-C", basedir,
  1057. f_name],
  1058. stdout=stream)
  1059. def handle_v3_file(self, f_name, subdir, stream, compressed=True,
  1060. encrypted=True):
  1061. # create inner archive
  1062. tar_cmdline = ["tar", "-Pc", '--sparse',
  1063. '-C', self.fullpath(os.path.dirname(f_name)),
  1064. '--xform', 's:^%s:%s\\0:' % (
  1065. os.path.basename(f_name),
  1066. subdir),
  1067. os.path.basename(f_name)
  1068. ]
  1069. if compressed:
  1070. tar_cmdline.insert(-1, "--use-compress-program=%s" % "gzip")
  1071. tar = subprocess.Popen(tar_cmdline, stdout=subprocess.PIPE)
  1072. if encrypted:
  1073. encryptor = subprocess.Popen(
  1074. ["openssl", "enc", "-e", "-aes-256-cbc", "-pass", "pass:qubes"],
  1075. stdin=tar.stdout,
  1076. stdout=subprocess.PIPE)
  1077. tar.stdout.close()
  1078. data = encryptor.stdout
  1079. else:
  1080. data = tar.stdout
  1081. encryptor = None
  1082. stage1_dir = self.fullpath(os.path.join("stage1", subdir))
  1083. if not os.path.exists(stage1_dir):
  1084. os.makedirs(stage1_dir)
  1085. subprocess.check_call(["split", "--numeric-suffixes",
  1086. "--suffix-length=3",
  1087. "--bytes="+str(100*1024*1024), "-",
  1088. os.path.join(stage1_dir,
  1089. os.path.basename(f_name+"."))],
  1090. stdin=data)
  1091. data.close()
  1092. tar.wait()
  1093. if encryptor:
  1094. encryptor.wait()
  1095. for part in sorted(os.listdir(stage1_dir)):
  1096. if not re.match(
  1097. r"^{}.[0-9][0-9][0-9]$".format(os.path.basename(f_name)),
  1098. part):
  1099. continue
  1100. part_with_dir = os.path.join(subdir, part)
  1101. self.calculate_hmac(os.path.join("stage1", part_with_dir))
  1102. self.append_backup_stream(part_with_dir, stream,
  1103. basedir=self.fullpath("stage1"))
  1104. self.append_backup_stream(part_with_dir+".hmac", stream,
  1105. basedir=self.fullpath("stage1"))
  1106. def handle_v4_file(self, f_name, subdir, stream, compressed="gzip"):
  1107. # create inner archive
  1108. tar_cmdline = ["tar", "-Pc", '--sparse',
  1109. '-C', self.fullpath(os.path.dirname(f_name)),
  1110. '--xform', 's:^%s:%s\\0:' % (
  1111. os.path.basename(f_name),
  1112. subdir),
  1113. os.path.basename(f_name)
  1114. ]
  1115. if compressed and isinstance(compressed, str):
  1116. tar_cmdline.insert(-1, "--use-compress-program=%s" % compressed)
  1117. elif compressed:
  1118. tar_cmdline.insert(-1, "--use-compress-program=%s" % "gzip")
  1119. tar = subprocess.Popen(tar_cmdline, stdout=subprocess.PIPE)
  1120. data = tar.stdout
  1121. stage1_dir = self.fullpath(os.path.join("stage1", subdir))
  1122. if not os.path.exists(stage1_dir):
  1123. os.makedirs(stage1_dir)
  1124. subprocess.check_call(["split", "--numeric-suffixes",
  1125. "--suffix-length=3",
  1126. "--bytes="+str(100*1024*1024), "-",
  1127. os.path.join(stage1_dir,
  1128. os.path.basename(f_name+"."))],
  1129. stdin=data)
  1130. data.close()
  1131. tar.wait()
  1132. for part in sorted(os.listdir(stage1_dir)):
  1133. if not re.match(
  1134. r"^{}.[0-9][0-9][0-9]$".format(os.path.basename(f_name)),
  1135. part):
  1136. continue
  1137. part_with_dir = os.path.join(subdir, part)
  1138. f_name = self.scrypt_encrypt(part_with_dir,
  1139. basedir=self.fullpath('stage1'))
  1140. self.append_backup_stream(f_name, stream,
  1141. basedir=self.fullpath("stage1"))
  1142. def create_v3_backup(self, encrypted=True, compressed=True):
  1143. """
  1144. Create "backup format 3" backup - used in R2 and R3.0
  1145. :param encrypted: Should the backup be encrypted
  1146. :param compressed: Should the backup be compressed
  1147. :return:
  1148. """
  1149. output = open(self.fullpath("backup.bin"), "w")
  1150. with open(self.fullpath("backup-header"), "w") as f:
  1151. f.write(BACKUP_HEADER_R2.format(
  1152. encrypted=str(encrypted),
  1153. compressed=str(compressed)
  1154. ))
  1155. self.calculate_hmac("backup-header")
  1156. self.append_backup_stream("backup-header", output)
  1157. self.append_backup_stream("backup-header.hmac", output)
  1158. with open(self.fullpath("qubes.xml"), "wb") as f:
  1159. qubesxml = pkg_resources.resource_string(__name__, 'v3-qubes.xml')
  1160. if encrypted:
  1161. for vmname, subdir in MANGLED_SUBDIRS_R2.items():
  1162. qubesxml = re.sub(r"[a-z-]*/{}".format(vmname).encode(),
  1163. subdir.encode(), qubesxml)
  1164. f.write(qubesxml)
  1165. else:
  1166. f.write(qubesxml)
  1167. self.handle_v3_file("qubes.xml", "", output, encrypted=encrypted,
  1168. compressed=compressed)
  1169. self.create_v1_files(r2b2=True)
  1170. for vm_type in ["appvms", "servicevms"]:
  1171. for vm_name in os.listdir(self.fullpath(vm_type)):
  1172. vm_dir = os.path.join(vm_type, vm_name)
  1173. for f_name in os.listdir(self.fullpath(vm_dir)):
  1174. if encrypted:
  1175. subdir = MANGLED_SUBDIRS_R2[vm_name]
  1176. else:
  1177. subdir = vm_dir
  1178. self.handle_v3_file(
  1179. os.path.join(vm_dir, f_name),
  1180. subdir+'/', output,
  1181. compressed=compressed,
  1182. encrypted=encrypted)
  1183. for vm_name in os.listdir(self.fullpath("vm-templates")):
  1184. vm_dir = os.path.join("vm-templates", vm_name)
  1185. if encrypted:
  1186. subdir = MANGLED_SUBDIRS_R2[vm_name]
  1187. else:
  1188. subdir = vm_dir
  1189. self.handle_v3_file(
  1190. os.path.join(vm_dir, "."),
  1191. subdir+'/', output,
  1192. compressed=compressed,
  1193. encrypted=encrypted)
  1194. self.handle_v3_file(
  1195. 'dom0-home/user',
  1196. 'dom0-home/', output,
  1197. compressed=compressed,
  1198. encrypted=encrypted)
  1199. output.close()
  1200. def create_v4_backup(self, compressed="gzip"):
  1201. """
  1202. Create "backup format 4" backup - used in R4.0
  1203. :param compressed: Should the backup be compressed
  1204. :return:
  1205. """
  1206. output = open(self.fullpath("backup.bin"), "w")
  1207. with open(self.fullpath("backup-header"), "w") as f:
  1208. f.write(BACKUP_HEADER_R4.format(
  1209. compressed=str(bool(compressed)),
  1210. compression_filter=(compressed if compressed else "gzip")
  1211. ))
  1212. self.scrypt_encrypt("backup-header", output_name='backup-header.hmac')
  1213. self.append_backup_stream("backup-header", output)
  1214. self.append_backup_stream("backup-header.hmac", output)
  1215. with open(self.fullpath("qubes.xml"), "wb") as f:
  1216. qubesxml = pkg_resources.resource_string(__name__, 'v4-qubes.xml')
  1217. for vmname, subdir in MANGLED_SUBDIRS_R4.items():
  1218. qubesxml = re.sub(
  1219. r'backup-path">[a-z-]*/{}'.format(vmname).encode(),
  1220. ('backup-path">' + subdir).encode(),
  1221. qubesxml)
  1222. f.write(qubesxml)
  1223. self.handle_v4_file("qubes.xml", "", output, compressed=compressed)
  1224. self.create_v4_files()
  1225. for vm_type in ["appvms", "vm-templates"]:
  1226. for vm_name in os.listdir(self.fullpath(vm_type)):
  1227. vm_dir = os.path.join(vm_type, vm_name)
  1228. for f_name in os.listdir(self.fullpath(vm_dir)):
  1229. subdir = MANGLED_SUBDIRS_R4[vm_name]
  1230. self.handle_v4_file(
  1231. os.path.join(vm_dir, f_name),
  1232. subdir+'/', output, compressed=compressed)
  1233. self.handle_v4_file(
  1234. 'dom0-home/user',
  1235. 'dom0-home/', output,
  1236. compressed=compressed)
  1237. output.close()
  1238. def setup_expected_calls(self, parsed_qubes_xml, templates_map=None):
  1239. if templates_map is None:
  1240. templates_map = {}
  1241. extra_vm_list_lines = []
  1242. for name, vm in parsed_qubes_xml['domains'].items():
  1243. if not vm['included_in_backup']:
  1244. continue
  1245. if name == 'dom0':
  1246. continue
  1247. if self.storage_pool:
  1248. self.app.expected_calls[
  1249. ('dom0', 'admin.vm.CreateInPool.' + vm['klass'],
  1250. templates_map.get(vm['template'], vm['template']),
  1251. 'name={} label={} pool={}'.format(
  1252. name, vm['label'], self.storage_pool).encode())] = \
  1253. b'0\0'
  1254. else:
  1255. self.app.expected_calls[
  1256. ('dom0', 'admin.vm.Create.' + vm['klass'],
  1257. templates_map.get(vm['template'], vm['template']),
  1258. 'name={} label={}'.format(name, vm['label']).encode())] =\
  1259. b'0\0'
  1260. extra_vm_list_lines.append(
  1261. '{} class={} state=Halted\n'.format(name, vm['klass']).encode())
  1262. if vm['backup_path']:
  1263. self.app.expected_calls[
  1264. (name, 'admin.vm.volume.List', None, None)] = \
  1265. b'0\0root\nprivate\nvolatile\n'
  1266. if vm['klass'] == 'AppVM':
  1267. self.app.expected_calls[
  1268. (name, 'admin.vm.volume.Info', 'root', None)] = \
  1269. b'0\0' \
  1270. b'pool=default\n' \
  1271. b'vid=' + name.encode() + b'/root\n' \
  1272. b'size=1024\n' \
  1273. b'usage=512\n' \
  1274. b'rw=False\n' \
  1275. b'snap_on_start=True\n' \
  1276. b'save_on_stop=False\n' \
  1277. b'source=\n' \
  1278. b'internal=True\n' \
  1279. b'revisions_to_keep=3\n'
  1280. else:
  1281. self.app.expected_calls[
  1282. (name, 'admin.vm.volume.Info', 'root', None)] = \
  1283. b'0\0' \
  1284. b'pool=default\n' \
  1285. b'vid=' + name.encode() + b'/root\n' \
  1286. b'size=1024\n' \
  1287. b'usage=512\n' \
  1288. b'rw=True\n' \
  1289. b'snap_on_start=False\n' \
  1290. b'save_on_stop=True\n' \
  1291. b'internal=True\n' \
  1292. b'revisions_to_keep=3\n'
  1293. self.app.expected_calls[
  1294. (name, 'admin.vm.volume.Resize', 'root',
  1295. str(vm.get('root_size', 2097664)).encode())] = \
  1296. b'0\0'
  1297. self.app.expected_calls[
  1298. (name, 'admin.vm.volume.Import', 'root',
  1299. name.encode() + b'/root')] = \
  1300. b'0\0'
  1301. self.app.expected_calls[
  1302. (name, 'admin.vm.volume.Info', 'private', None)] = \
  1303. b'0\0' \
  1304. b'pool=default\n' \
  1305. b'vid=' + name.encode() + b'/private\n' \
  1306. b'size=1024\n' \
  1307. b'usage=512\n' \
  1308. b'rw=True\n' \
  1309. b'snap_on_start=False\n' \
  1310. b'save_on_stop=True\n' \
  1311. b'revisions_to_keep=3\n'
  1312. self.app.expected_calls[
  1313. (name, 'admin.vm.volume.Resize', 'private', b'2097152')] = \
  1314. b'0\0'
  1315. self.app.expected_calls[
  1316. (name, 'admin.vm.volume.Import', 'private',
  1317. name.encode() + b'/private')] = \
  1318. b'0\0'
  1319. self.app.expected_calls[
  1320. (name, 'admin.vm.volume.Info', 'volatile', None)] = \
  1321. b'0\0' \
  1322. b'pool=default\n' \
  1323. b'vid=' + name.encode() + b'/root\n' \
  1324. b'size=1024\n' \
  1325. b'usage=512\n' \
  1326. b'rw=True\n' \
  1327. b'snap_on_start=False\n' \
  1328. b'save_on_stop=False\n' \
  1329. b'revisions_to_keep=3\n'
  1330. for prop, value in vm['properties'].items():
  1331. self.app.expected_calls[
  1332. (name, 'admin.vm.property.Set', prop,
  1333. str(value).encode() if value is not None else b'')] = b'0\0'
  1334. for bus, devices in vm['devices'].items():
  1335. for (backend_domain, ident), options in devices.items():
  1336. all_options = options.copy()
  1337. all_options['persistent'] = True
  1338. encoded_options = ' '.join('{}={}'.format(key, value) for
  1339. key, value in all_options.items()).encode()
  1340. self.app.expected_calls[
  1341. (name, 'admin.vm.device.{}.Attach'.format(bus),
  1342. '{}+{}'.format(backend_domain, ident),
  1343. encoded_options)] = b'0\0'
  1344. for feature, value in vm['features'].items():
  1345. if value is False:
  1346. value = ''
  1347. elif value is True:
  1348. value = '1'
  1349. self.app.expected_calls[
  1350. (name, 'admin.vm.feature.Set', feature,
  1351. str(value).encode())] = b'0\0'
  1352. for tag in vm['tags']:
  1353. if tag.startswith('created-by-'):
  1354. self.app.expected_calls[
  1355. (name, 'admin.vm.tag.Set', tag, None)] = b''
  1356. self.app.expected_calls[
  1357. (name, 'admin.vm.tag.Get', tag, None)] = b'0\0001'
  1358. else:
  1359. self.app.expected_calls[
  1360. (name, 'admin.vm.tag.Set', tag, None)] = b'0\0'
  1361. if vm['backup_path']:
  1362. appmenus = (
  1363. b'gnome-terminal.desktop\n'
  1364. b'nautilus.desktop\n'
  1365. b'firefox.desktop\n'
  1366. b'mozilla-thunderbird.desktop\n'
  1367. b'libreoffice-startcenter.desktop\n'
  1368. )
  1369. self.app.expected_calls[
  1370. (name, 'appmenus', None, appmenus)] = b'0\0'
  1371. orig_admin_vm_list = self.app.expected_calls[
  1372. ('dom0', 'admin.vm.List', None, None)]
  1373. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1374. [orig_admin_vm_list] + \
  1375. [orig_admin_vm_list + b''.join(extra_vm_list_lines)] * \
  1376. len(extra_vm_list_lines)
  1377. def mock_appmenus(self, queue, vm, stream):
  1378. queue.put((vm.name, 'appmenus', None, stream.read()))
  1379. def test_210_r2(self):
  1380. self.create_v3_backup(False)
  1381. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1382. b'0\0dom0 class=AdminVM state=Running\n'
  1383. b'fedora-25 class=TemplateVM state=Halted\n'
  1384. b'testvm class=AppVM state=Running\n'
  1385. )
  1386. self.app.expected_calls[
  1387. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1388. b'0\0default=no type=vm fedora-25'
  1389. self.setup_expected_calls(parsed_qubes_xml_r2, templates_map={
  1390. 'fedora-20-x64': 'fedora-25'
  1391. })
  1392. firewall_data = (
  1393. 'action=accept specialtarget=dns\n'
  1394. 'action=accept proto=icmp\n'
  1395. 'action=accept proto=tcp dstports=22-22\n'
  1396. 'action=accept proto=tcp dstports=9418-9418\n'
  1397. 'action=accept proto=tcp dst4=192.168.0.1/32 dstports=1234-1234\n'
  1398. 'action=accept proto=tcp dsthost=fedorahosted.org dstports=443-443\n'
  1399. 'action=accept proto=tcp dsthost=xenbits.xen.org dstports=80-80\n'
  1400. 'action=drop\n'
  1401. )
  1402. self.app.expected_calls[
  1403. ('test-work', 'admin.vm.firewall.Set', None,
  1404. firewall_data.encode())] = b'0\0'
  1405. self.app.expected_calls[
  1406. ('test-custom-template-appvm', 'admin.vm.firewall.Set', None,
  1407. firewall_data.encode())] = b'0\0'
  1408. qubesd_calls_queue = multiprocessing.Queue()
  1409. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1410. patches = [
  1411. mock.patch('qubesadmin.storage.Volume',
  1412. functools.partial(MockVolume, qubesd_calls_queue)),
  1413. mock.patch(
  1414. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1415. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1416. mock.patch(
  1417. 'qubesadmin.firewall.Firewall',
  1418. functools.partial(MockFirewall, qubesd_calls_queue)),
  1419. mock.patch(
  1420. 'time.strftime',
  1421. return_value=dummy_timestamp)
  1422. ]
  1423. for patch in patches:
  1424. patch.start()
  1425. try:
  1426. self.restore_backup(self.fullpath("backup.bin"), options={
  1427. 'use-default-template': True,
  1428. 'use-default-netvm': True,
  1429. })
  1430. finally:
  1431. for patch in patches:
  1432. patch.stop()
  1433. # retrieve calls from other multiprocess.Process instances
  1434. while not qubesd_calls_queue.empty():
  1435. call_args = qubesd_calls_queue.get()
  1436. self.app.qubesd_call(*call_args)
  1437. qubesd_calls_queue.close()
  1438. self.assertAllCalled()
  1439. self.assertDom0Restored(dummy_timestamp)
  1440. def test_220_r2_encrypted(self):
  1441. self.create_v3_backup(True)
  1442. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1443. b'0\0dom0 class=AdminVM state=Running\n'
  1444. b'fedora-25 class=TemplateVM state=Halted\n'
  1445. b'testvm class=AppVM state=Running\n'
  1446. )
  1447. self.app.expected_calls[
  1448. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1449. b'0\0default=no type=vm fedora-25'
  1450. self.setup_expected_calls(parsed_qubes_xml_r2, templates_map={
  1451. 'fedora-20-x64': 'fedora-25'
  1452. })
  1453. firewall_data = (
  1454. 'action=accept specialtarget=dns\n'
  1455. 'action=accept proto=icmp\n'
  1456. 'action=accept proto=tcp dstports=22-22\n'
  1457. 'action=accept proto=tcp dstports=9418-9418\n'
  1458. 'action=accept proto=tcp dst4=192.168.0.1/32 dstports=1234-1234\n'
  1459. 'action=accept proto=tcp dsthost=fedorahosted.org dstports=443-443\n'
  1460. 'action=accept proto=tcp dsthost=xenbits.xen.org dstports=80-80\n'
  1461. 'action=drop\n'
  1462. )
  1463. self.app.expected_calls[
  1464. ('test-work', 'admin.vm.firewall.Set', None,
  1465. firewall_data.encode())] = b'0\0'
  1466. self.app.expected_calls[
  1467. ('test-custom-template-appvm', 'admin.vm.firewall.Set', None,
  1468. firewall_data.encode())] = b'0\0'
  1469. qubesd_calls_queue = multiprocessing.Queue()
  1470. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1471. patches = [
  1472. mock.patch('qubesadmin.storage.Volume',
  1473. functools.partial(MockVolume, qubesd_calls_queue)),
  1474. mock.patch(
  1475. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1476. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1477. mock.patch(
  1478. 'qubesadmin.firewall.Firewall',
  1479. functools.partial(MockFirewall, qubesd_calls_queue)),
  1480. mock.patch(
  1481. 'time.strftime',
  1482. return_value=dummy_timestamp)
  1483. ]
  1484. for patch in patches:
  1485. patch.start()
  1486. try:
  1487. self.restore_backup(self.fullpath("backup.bin"), options={
  1488. 'use-default-template': True,
  1489. 'use-default-netvm': True,
  1490. })
  1491. finally:
  1492. for patch in patches:
  1493. patch.stop()
  1494. # retrieve calls from other multiprocess.Process instances
  1495. while not qubesd_calls_queue.empty():
  1496. call_args = qubesd_calls_queue.get()
  1497. self.app.qubesd_call(*call_args)
  1498. qubesd_calls_queue.close()
  1499. self.assertAllCalled()
  1500. self.assertDom0Restored(dummy_timestamp)
  1501. def test_230_r2_uncompressed(self):
  1502. self.create_v3_backup(False, False)
  1503. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1504. b'0\0dom0 class=AdminVM state=Running\n'
  1505. b'fedora-25 class=TemplateVM state=Halted\n'
  1506. b'testvm class=AppVM state=Running\n'
  1507. )
  1508. self.app.expected_calls[
  1509. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1510. b'0\0default=no type=vm fedora-25'
  1511. self.setup_expected_calls(parsed_qubes_xml_r2, templates_map={
  1512. 'fedora-20-x64': 'fedora-25'
  1513. })
  1514. firewall_data = (
  1515. 'action=accept specialtarget=dns\n'
  1516. 'action=accept proto=icmp\n'
  1517. 'action=accept proto=tcp dstports=22-22\n'
  1518. 'action=accept proto=tcp dstports=9418-9418\n'
  1519. 'action=accept proto=tcp dst4=192.168.0.1/32 dstports=1234-1234\n'
  1520. 'action=accept proto=tcp dsthost=fedorahosted.org dstports=443-443\n'
  1521. 'action=accept proto=tcp dsthost=xenbits.xen.org dstports=80-80\n'
  1522. 'action=drop\n'
  1523. )
  1524. self.app.expected_calls[
  1525. ('test-work', 'admin.vm.firewall.Set', None,
  1526. firewall_data.encode())] = b'0\0'
  1527. self.app.expected_calls[
  1528. ('test-custom-template-appvm', 'admin.vm.firewall.Set', None,
  1529. firewall_data.encode())] = b'0\0'
  1530. qubesd_calls_queue = multiprocessing.Queue()
  1531. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1532. patches = [
  1533. mock.patch('qubesadmin.storage.Volume',
  1534. functools.partial(MockVolume, qubesd_calls_queue)),
  1535. mock.patch(
  1536. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1537. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1538. mock.patch(
  1539. 'qubesadmin.firewall.Firewall',
  1540. functools.partial(MockFirewall, qubesd_calls_queue)),
  1541. mock.patch(
  1542. 'time.strftime',
  1543. return_value=dummy_timestamp)
  1544. ]
  1545. for patch in patches:
  1546. patch.start()
  1547. try:
  1548. self.restore_backup(self.fullpath("backup.bin"), options={
  1549. 'use-default-template': True,
  1550. 'use-default-netvm': True,
  1551. })
  1552. finally:
  1553. for patch in patches:
  1554. patch.stop()
  1555. # retrieve calls from other multiprocess.Process instances
  1556. while not qubesd_calls_queue.empty():
  1557. call_args = qubesd_calls_queue.get()
  1558. self.app.qubesd_call(*call_args)
  1559. qubesd_calls_queue.close()
  1560. self.assertAllCalled()
  1561. self.assertDom0Restored(dummy_timestamp)
  1562. @unittest.skipUnless(spawn.find_executable('scrypt'),
  1563. "scrypt not installed")
  1564. def test_230_r4(self):
  1565. self.create_v4_backup("")
  1566. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1567. b'0\0dom0 class=AdminVM state=Running\n'
  1568. b'fedora-25 class=TemplateVM state=Halted\n'
  1569. b'testvm class=AppVM state=Running\n'
  1570. b'sys-net class=AppVM state=Running\n'
  1571. )
  1572. self.app.expected_calls[
  1573. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1574. b'0\0default=no type=vm fedora-25'
  1575. self.app.expected_calls[
  1576. ('sys-net', 'admin.vm.property.Get', 'provides_network', None)] = \
  1577. b'0\0default=no type=bool True'
  1578. self.setup_expected_calls(parsed_qubes_xml_v4, templates_map={
  1579. 'debian-8': 'fedora-25'
  1580. })
  1581. firewall_data = (
  1582. 'action=accept specialtarget=dns\n'
  1583. 'action=accept proto=icmp\n'
  1584. 'action=accept proto=tcp dstports=22-22\n'
  1585. 'action=accept proto=tcp dsthost=www.qubes-os.org '
  1586. 'dstports=443-443\n'
  1587. 'action=accept proto=tcp dst4=192.168.0.0/24\n'
  1588. 'action=drop\n'
  1589. )
  1590. self.app.expected_calls[
  1591. ('test-work', 'admin.vm.firewall.Set', None,
  1592. firewall_data.encode())] = b'0\0'
  1593. qubesd_calls_queue = multiprocessing.Queue()
  1594. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1595. patches = [
  1596. mock.patch('qubesadmin.storage.Volume',
  1597. functools.partial(MockVolume, qubesd_calls_queue)),
  1598. mock.patch(
  1599. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1600. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1601. mock.patch(
  1602. 'qubesadmin.firewall.Firewall',
  1603. functools.partial(MockFirewall, qubesd_calls_queue)),
  1604. mock.patch(
  1605. 'time.strftime',
  1606. return_value=dummy_timestamp)
  1607. ]
  1608. for patch in patches:
  1609. patch.start()
  1610. try:
  1611. self.restore_backup(self.fullpath("backup.bin"), options={
  1612. 'use-default-template': True,
  1613. 'use-default-netvm': True,
  1614. })
  1615. finally:
  1616. for patch in patches:
  1617. patch.stop()
  1618. # retrieve calls from other multiprocess.Process instances
  1619. while not qubesd_calls_queue.empty():
  1620. call_args = qubesd_calls_queue.get()
  1621. with contextlib.suppress(qubesadmin.exc.QubesException):
  1622. self.app.qubesd_call(*call_args)
  1623. qubesd_calls_queue.close()
  1624. self.assertAllCalled()
  1625. self.assertDom0Restored(dummy_timestamp)
  1626. @unittest.skipUnless(spawn.find_executable('scrypt'),
  1627. "scrypt not installed")
  1628. def test_230_r4_compressed(self):
  1629. self.create_v4_backup("gzip")
  1630. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1631. b'0\0dom0 class=AdminVM state=Running\n'
  1632. b'fedora-25 class=TemplateVM state=Halted\n'
  1633. b'testvm class=AppVM state=Running\n'
  1634. b'sys-net class=AppVM state=Running\n'
  1635. )
  1636. self.app.expected_calls[
  1637. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1638. b'0\0default=no type=vm fedora-25'
  1639. self.app.expected_calls[
  1640. ('sys-net', 'admin.vm.property.Get', 'provides_network', None)] = \
  1641. b'0\0default=no type=bool True'
  1642. self.setup_expected_calls(parsed_qubes_xml_v4, templates_map={
  1643. 'debian-8': 'fedora-25'
  1644. })
  1645. firewall_data = (
  1646. 'action=accept specialtarget=dns\n'
  1647. 'action=accept proto=icmp\n'
  1648. 'action=accept proto=tcp dstports=22-22\n'
  1649. 'action=accept proto=tcp dsthost=www.qubes-os.org '
  1650. 'dstports=443-443\n'
  1651. 'action=accept proto=tcp dst4=192.168.0.0/24\n'
  1652. 'action=drop\n'
  1653. )
  1654. self.app.expected_calls[
  1655. ('test-work', 'admin.vm.firewall.Set', None,
  1656. firewall_data.encode())] = b'0\0'
  1657. qubesd_calls_queue = multiprocessing.Queue()
  1658. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1659. patches = [
  1660. mock.patch('qubesadmin.storage.Volume',
  1661. functools.partial(MockVolume, qubesd_calls_queue)),
  1662. mock.patch(
  1663. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1664. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1665. mock.patch(
  1666. 'qubesadmin.firewall.Firewall',
  1667. functools.partial(MockFirewall, qubesd_calls_queue)),
  1668. mock.patch(
  1669. 'time.strftime',
  1670. return_value=dummy_timestamp)
  1671. ]
  1672. for patch in patches:
  1673. patch.start()
  1674. try:
  1675. self.restore_backup(self.fullpath("backup.bin"), options={
  1676. 'use-default-template': True,
  1677. 'use-default-netvm': True,
  1678. })
  1679. finally:
  1680. for patch in patches:
  1681. patch.stop()
  1682. # retrieve calls from other multiprocess.Process instances
  1683. while not qubesd_calls_queue.empty():
  1684. call_args = qubesd_calls_queue.get()
  1685. with contextlib.suppress(qubesadmin.exc.QubesException):
  1686. self.app.qubesd_call(*call_args)
  1687. qubesd_calls_queue.close()
  1688. self.assertAllCalled()
  1689. self.assertDom0Restored(dummy_timestamp)
  1690. @unittest.skipUnless(spawn.find_executable('scrypt'),
  1691. "scrypt not installed")
  1692. def test_230_r4_custom_cmpression(self):
  1693. self.create_v4_backup("bzip2")
  1694. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1695. b'0\0dom0 class=AdminVM state=Running\n'
  1696. b'fedora-25 class=TemplateVM state=Halted\n'
  1697. b'testvm class=AppVM state=Running\n'
  1698. b'sys-net class=AppVM state=Running\n'
  1699. )
  1700. self.app.expected_calls[
  1701. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1702. b'0\0default=no type=vm fedora-25'
  1703. self.app.expected_calls[
  1704. ('sys-net', 'admin.vm.property.Get', 'provides_network', None)] = \
  1705. b'0\0default=no type=bool True'
  1706. self.setup_expected_calls(parsed_qubes_xml_v4, templates_map={
  1707. 'debian-8': 'fedora-25'
  1708. })
  1709. firewall_data = (
  1710. 'action=accept specialtarget=dns\n'
  1711. 'action=accept proto=icmp\n'
  1712. 'action=accept proto=tcp dstports=22-22\n'
  1713. 'action=accept proto=tcp dsthost=www.qubes-os.org '
  1714. 'dstports=443-443\n'
  1715. 'action=accept proto=tcp dst4=192.168.0.0/24\n'
  1716. 'action=drop\n'
  1717. )
  1718. self.app.expected_calls[
  1719. ('test-work', 'admin.vm.firewall.Set', None,
  1720. firewall_data.encode())] = b'0\0'
  1721. qubesd_calls_queue = multiprocessing.Queue()
  1722. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1723. patches = [
  1724. mock.patch('qubesadmin.storage.Volume',
  1725. functools.partial(MockVolume, qubesd_calls_queue)),
  1726. mock.patch(
  1727. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1728. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1729. mock.patch(
  1730. 'qubesadmin.firewall.Firewall',
  1731. functools.partial(MockFirewall, qubesd_calls_queue)),
  1732. mock.patch(
  1733. 'time.strftime',
  1734. return_value=dummy_timestamp)
  1735. ]
  1736. for patch in patches:
  1737. patch.start()
  1738. try:
  1739. self.restore_backup(self.fullpath("backup.bin"), options={
  1740. 'use-default-template': True,
  1741. 'use-default-netvm': True,
  1742. })
  1743. finally:
  1744. for patch in patches:
  1745. patch.stop()
  1746. # retrieve calls from other multiprocess.Process instances
  1747. while not qubesd_calls_queue.empty():
  1748. call_args = qubesd_calls_queue.get()
  1749. with contextlib.suppress(qubesadmin.exc.QubesException):
  1750. self.app.qubesd_call(*call_args)
  1751. qubesd_calls_queue.close()
  1752. self.assertAllCalled()
  1753. self.assertDom0Restored(dummy_timestamp)
  1754. @unittest.skipUnless(spawn.find_executable('scrypt'),
  1755. "scrypt not installed")
  1756. def test_230_r4_uncommon_compression(self):
  1757. self.create_v4_backup("less")
  1758. qubesd_calls_queue = multiprocessing.Queue()
  1759. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1760. patches = [
  1761. mock.patch('qubesadmin.storage.Volume',
  1762. functools.partial(MockVolume, qubesd_calls_queue)),
  1763. mock.patch(
  1764. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1765. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1766. mock.patch(
  1767. 'qubesadmin.firewall.Firewall',
  1768. functools.partial(MockFirewall, qubesd_calls_queue)),
  1769. mock.patch(
  1770. 'time.strftime',
  1771. return_value=dummy_timestamp)
  1772. ]
  1773. for patch in patches:
  1774. patch.start()
  1775. try:
  1776. with self.assertRaises(qubesadmin.exc.QubesException):
  1777. qubesadmin.backup.restore.BackupRestore(
  1778. self.app, self.fullpath("backup.bin"), None, 'qubes')
  1779. finally:
  1780. for patch in patches:
  1781. patch.stop()
  1782. @unittest.skipUnless(spawn.find_executable('scrypt'),
  1783. "scrypt not installed")
  1784. def test_230_r4_uncommon_cmpression_forced(self):
  1785. self.create_v4_backup("less")
  1786. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  1787. b'0\0dom0 class=AdminVM state=Running\n'
  1788. b'fedora-25 class=TemplateVM state=Halted\n'
  1789. b'testvm class=AppVM state=Running\n'
  1790. b'sys-net class=AppVM state=Running\n'
  1791. )
  1792. self.app.expected_calls[
  1793. ('dom0', 'admin.property.Get', 'default_template', None)] = \
  1794. b'0\0default=no type=vm fedora-25'
  1795. self.app.expected_calls[
  1796. ('sys-net', 'admin.vm.property.Get', 'provides_network', None)] = \
  1797. b'0\0default=no type=bool True'
  1798. self.setup_expected_calls(parsed_qubes_xml_v4, templates_map={
  1799. 'debian-8': 'fedora-25'
  1800. })
  1801. firewall_data = (
  1802. 'action=accept specialtarget=dns\n'
  1803. 'action=accept proto=icmp\n'
  1804. 'action=accept proto=tcp dstports=22-22\n'
  1805. 'action=accept proto=tcp dsthost=www.qubes-os.org '
  1806. 'dstports=443-443\n'
  1807. 'action=accept proto=tcp dst4=192.168.0.0/24\n'
  1808. 'action=drop\n'
  1809. )
  1810. self.app.expected_calls[
  1811. ('test-work', 'admin.vm.firewall.Set', None,
  1812. firewall_data.encode())] = b'0\0'
  1813. qubesd_calls_queue = multiprocessing.Queue()
  1814. dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
  1815. patches = [
  1816. mock.patch('qubesadmin.storage.Volume',
  1817. functools.partial(MockVolume, qubesd_calls_queue)),
  1818. mock.patch(
  1819. 'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
  1820. functools.partial(self.mock_appmenus, qubesd_calls_queue)),
  1821. mock.patch(
  1822. 'qubesadmin.firewall.Firewall',
  1823. functools.partial(MockFirewall, qubesd_calls_queue)),
  1824. mock.patch(
  1825. 'time.strftime',
  1826. return_value=dummy_timestamp)
  1827. ]
  1828. for patch in patches:
  1829. patch.start()
  1830. try:
  1831. self.restore_backup(self.fullpath("backup.bin"), options={
  1832. 'use-default-template': True,
  1833. 'use-default-netvm': True,
  1834. }, force_compression_filter='less')
  1835. finally:
  1836. for patch in patches:
  1837. patch.stop()
  1838. # retrieve calls from other multiprocess.Process instances
  1839. while not qubesd_calls_queue.empty():
  1840. call_args = qubesd_calls_queue.get()
  1841. with contextlib.suppress(qubesadmin.exc.QubesException):
  1842. self.app.qubesd_call(*call_args)
  1843. qubesd_calls_queue.close()
  1844. self.assertAllCalled()
  1845. self.assertDom0Restored(dummy_timestamp)
  1846. class TC_11_BackupCompatibilityIntoLVM(TC_10_BackupCompatibility):
  1847. storage_pool = 'some-pool'
  1848. def restore_backup(self, source=None, appvm=None, options=None,
  1849. **kwargs):
  1850. if options is None:
  1851. options = {}
  1852. options['override_pool'] = self.storage_pool
  1853. super(TC_11_BackupCompatibilityIntoLVM, self).restore_backup(source,
  1854. appvm, options, **kwargs)