network.py 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609
  1. #
  2. # The Qubes OS Project, https://www.qubes-os.org/
  3. #
  4. # Copyright (C) 2015
  5. # Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  6. # Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  7. #
  8. # This library is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU Lesser General Public
  10. # License as published by the Free Software Foundation; either
  11. # version 2.1 of the License, or (at your option) any later version.
  12. #
  13. # This library is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. # Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public
  19. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  20. #
  21. from distutils import spawn
  22. import asyncio
  23. import subprocess
  24. import sys
  25. import time
  26. import unittest
  27. import qubes.tests
  28. import qubes.firewall
  29. import qubes.vm.qubesvm
  30. import qubes.vm.appvm
  31. # noinspection PyAttributeOutsideInit,PyPep8Naming
  32. class VmNetworkingMixin(object):
  33. test_ip = '192.168.123.45'
  34. test_name = 'test.example.com'
  35. ping_cmd = 'ping -W 1 -n -c 1 {target}'
  36. ping_ip = ping_cmd.format(target=test_ip)
  37. ping_name = ping_cmd.format(target=test_name)
  38. # filled by load_tests
  39. template = None
  40. def run_cmd(self, vm, cmd, user="root"):
  41. '''Run a command *cmd* in a *vm* as *user*. Return its exit code.
  42. :type self: qubes.tests.SystemTestCase | VmNetworkingMixin
  43. :param qubes.vm.qubesvm.QubesVM vm: VM object to run command in
  44. :param str cmd: command to execute
  45. :param std user: user to execute command as
  46. :return int: command exit code
  47. '''
  48. try:
  49. self.loop.run_until_complete(vm.run_for_stdio(cmd, user=user))
  50. except subprocess.CalledProcessError as e:
  51. return e.returncode
  52. return 0
  53. def setUp(self):
  54. '''
  55. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  56. '''
  57. super(VmNetworkingMixin, self).setUp()
  58. if self.template.startswith('whonix-'):
  59. self.skipTest("Test not supported here - Whonix uses its own "
  60. "firewall settings")
  61. if self.template.endswith('-minimal'):
  62. self.skipTest(
  63. "Test not supported here - minimal template don't have "
  64. "networking packages by default")
  65. self.init_default_template(self.template)
  66. self.testnetvm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  67. name=self.make_vm_name('netvm1'),
  68. label='red')
  69. self.loop.run_until_complete(self.testnetvm.create_on_disk())
  70. self.testnetvm.provides_network = True
  71. self.testnetvm.netvm = None
  72. self.testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  73. name=self.make_vm_name('vm1'),
  74. label='red')
  75. self.loop.run_until_complete(self.testvm1.create_on_disk())
  76. self.testvm1.netvm = self.testnetvm
  77. self.app.save()
  78. self.configure_netvm()
  79. def configure_netvm(self):
  80. '''
  81. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  82. '''
  83. def run_netvm_cmd(cmd):
  84. try:
  85. self.loop.run_until_complete(
  86. self.testnetvm.run_for_stdio(cmd, user='root'))
  87. except subprocess.CalledProcessError as e:
  88. self.fail("Command '%s' failed: %s%s" %
  89. (cmd, e.stdout.decode(), e.stderr.decode()))
  90. if not self.testnetvm.is_running():
  91. self.loop.run_until_complete(self.testnetvm.start())
  92. # Ensure that dnsmasq is installed:
  93. try:
  94. self.loop.run_until_complete(self.testnetvm.run_for_stdio(
  95. 'dnsmasq --version', user='root'))
  96. except subprocess.CalledProcessError:
  97. self.skipTest("dnsmasq not installed")
  98. run_netvm_cmd("ip link add test0 type dummy")
  99. run_netvm_cmd("ip link set test0 up")
  100. run_netvm_cmd("ip addr add {}/24 dev test0".format(self.test_ip))
  101. run_netvm_cmd("iptables -I INPUT -d {} -j ACCEPT --wait".format(
  102. self.test_ip))
  103. # ignore failure
  104. self.run_cmd(self.testnetvm, "pkill dnsmasq")
  105. run_netvm_cmd("dnsmasq -a {ip} -A /{name}/{ip} -i test0 -z".format(
  106. ip=self.test_ip, name=self.test_name))
  107. run_netvm_cmd("echo nameserver {} > /etc/resolv.conf".format(
  108. self.test_ip))
  109. run_netvm_cmd("/usr/lib/qubes/qubes-setup-dnat-to-ns")
  110. def test_000_simple_networking(self):
  111. '''
  112. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  113. '''
  114. self.loop.run_until_complete(self.testvm1.start())
  115. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  116. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  117. def test_010_simple_proxyvm(self):
  118. '''
  119. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  120. '''
  121. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  122. name=self.make_vm_name('proxy'),
  123. label='red')
  124. self.proxy.provides_network = True
  125. self.proxy.netvm = self.testnetvm
  126. self.loop.run_until_complete(self.proxy.create_on_disk())
  127. self.testvm1.netvm = self.proxy
  128. self.app.save()
  129. self.loop.run_until_complete(self.testvm1.start())
  130. self.assertTrue(self.proxy.is_running())
  131. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  132. "Ping by IP from ProxyVM failed")
  133. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  134. "Ping by name from ProxyVM failed")
  135. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  136. "Ping by IP from AppVM failed")
  137. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  138. "Ping by IP from AppVM failed")
  139. @qubes.tests.expectedFailureIfTemplate('debian-7')
  140. @unittest.skipUnless(spawn.find_executable('xdotool'),
  141. "xdotool not installed")
  142. def test_020_simple_proxyvm_nm(self):
  143. '''
  144. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  145. '''
  146. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  147. name=self.make_vm_name('proxy'),
  148. label='red')
  149. self.proxy.provides_network = True
  150. self.loop.run_until_complete(self.proxy.create_on_disk())
  151. self.proxy.netvm = self.testnetvm
  152. self.proxy.features['service.network-manager'] = True
  153. self.testvm1.netvm = self.proxy
  154. self.app.save()
  155. self.loop.run_until_complete(self.testvm1.start())
  156. self.assertTrue(self.proxy.is_running())
  157. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  158. "Ping by IP failed")
  159. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  160. "Ping by name failed")
  161. # reconnect to make sure that device was configured by NM
  162. self.assertEqual(
  163. self.run_cmd(self.proxy, "nmcli device disconnect eth0",
  164. user="user"),
  165. 0, "Failed to disconnect eth0 using nmcli")
  166. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  167. "Network should be disabled, but apparently it isn't")
  168. self.assertEqual(
  169. self.run_cmd(self.proxy,
  170. 'nmcli connection up "VM uplink eth0" ifname eth0',
  171. user="user"),
  172. 0, "Failed to connect eth0 using nmcli")
  173. self.assertEqual(self.run_cmd(self.proxy, "nm-online", user="user"), 0,
  174. "Failed to wait for NM connection")
  175. # check for nm-applet presence
  176. self.assertEqual(subprocess.call([
  177. 'xdotool', 'search', '--class', '{}:nm-applet'.format(
  178. self.proxy.name)],
  179. stdout=subprocess.DEVNULL), 0, "nm-applet window not found")
  180. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  181. "Ping by IP failed (after NM reconnection")
  182. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  183. "Ping by name failed (after NM reconnection)")
  184. def test_030_firewallvm_firewall(self):
  185. '''
  186. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  187. '''
  188. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  189. name=self.make_vm_name('proxy'),
  190. label='red')
  191. self.proxy.provides_network = True
  192. self.loop.run_until_complete(self.proxy.create_on_disk())
  193. self.proxy.netvm = self.testnetvm
  194. self.testvm1.netvm = self.proxy
  195. self.app.save()
  196. # block all for first
  197. self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')]
  198. self.testvm1.firewall.save()
  199. self.loop.run_until_complete(self.testvm1.start())
  200. self.assertTrue(self.proxy.is_running())
  201. server = self.loop.run_until_complete(self.testnetvm.run(
  202. 'socat TCP-LISTEN:1234,fork EXEC:/bin/uname'))
  203. try:
  204. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  205. "Ping by IP from ProxyVM failed")
  206. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  207. "Ping by name from ProxyVM failed")
  208. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  209. "Ping by IP should be blocked")
  210. client_cmd = "socat TCP:{}:1234 -".format(self.test_ip)
  211. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  212. "TCP connection should be blocked")
  213. # block all except ICMP
  214. self.testvm1.firewall.rules = [(
  215. qubes.firewall.Rule(None, action='accept', proto='icmp')
  216. )]
  217. self.testvm1.firewall.save()
  218. # Ugly hack b/c there is no feedback when the rules are actually
  219. # applied
  220. time.sleep(3)
  221. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  222. "Ping by IP failed (should be allowed now)")
  223. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  224. "Ping by name should be blocked")
  225. # all TCP still blocked
  226. self.testvm1.firewall.rules = [
  227. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  228. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  229. ]
  230. self.testvm1.firewall.save()
  231. # Ugly hack b/c there is no feedback when the rules are actually
  232. # applied
  233. time.sleep(3)
  234. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  235. "Ping by name failed (should be allowed now)")
  236. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  237. "TCP connection should be blocked")
  238. # block all except target
  239. self.testvm1.firewall.rules = [
  240. qubes.firewall.Rule(None, action='accept', dsthost=self.test_ip,
  241. proto='tcp', dstports=1234),
  242. ]
  243. self.testvm1.firewall.save()
  244. # Ugly hack b/c there is no feedback when the rules are actually
  245. # applied
  246. time.sleep(3)
  247. self.assertEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  248. "TCP connection failed (should be allowed now)")
  249. # allow all except target
  250. self.testvm1.firewall.rules = [
  251. qubes.firewall.Rule(None, action='drop', dsthost=self.test_ip,
  252. proto='tcp', dstports=1234),
  253. qubes.firewall.Rule(action='accept'),
  254. ]
  255. self.testvm1.firewall.save()
  256. # Ugly hack b/c there is no feedback when the rules are actually
  257. # applied
  258. time.sleep(3)
  259. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  260. "TCP connection should be blocked")
  261. finally:
  262. server.terminate()
  263. self.loop.run_until_complete(server.wait())
  264. def test_040_inter_vm(self):
  265. '''
  266. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  267. '''
  268. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  269. name=self.make_vm_name('proxy'),
  270. label='red')
  271. self.loop.run_until_complete(self.proxy.create_on_disk())
  272. self.proxy.provides_network = True
  273. self.proxy.netvm = self.testnetvm
  274. self.testvm1.netvm = self.proxy
  275. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  276. name=self.make_vm_name('vm2'),
  277. label='red')
  278. self.loop.run_until_complete(self.testvm2.create_on_disk())
  279. self.testvm2.netvm = self.proxy
  280. self.app.save()
  281. self.loop.run_until_complete(asyncio.wait([
  282. self.testvm1.start(),
  283. self.testvm2.start()]))
  284. self.assertNotEqual(self.run_cmd(self.testvm1,
  285. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  286. self.testvm2.netvm = self.testnetvm
  287. self.assertNotEqual(self.run_cmd(self.testvm1,
  288. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  289. self.assertNotEqual(self.run_cmd(self.testvm2,
  290. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  291. self.testvm1.netvm = self.testnetvm
  292. self.assertNotEqual(self.run_cmd(self.testvm1,
  293. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  294. self.assertNotEqual(self.run_cmd(self.testvm2,
  295. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  296. def test_050_spoof_ip(self):
  297. '''Test if VM IP spoofing is blocked
  298. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  299. '''
  300. self.loop.run_until_complete(self.testvm1.start())
  301. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  302. self.assertEqual(self.run_cmd(self.testnetvm,
  303. 'iptables -I INPUT -i vif+ ! -s {} -p icmp -j LOG'.format(
  304. self.testvm1.ip)), 0)
  305. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  306. 'ip addr flush dev eth0 && '
  307. 'ip addr add 10.137.1.128/24 dev eth0 && '
  308. 'ip route add default dev eth0',
  309. user='root'))
  310. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  311. "Spoofed ping should be blocked")
  312. try:
  313. (output, _) = self.loop.run_until_complete(
  314. self.testnetvm.run_for_stdio('iptables -nxvL INPUT',
  315. user='root'))
  316. except subprocess.CalledProcessError:
  317. self.fail('iptables -nxvL INPUT failed')
  318. output = output.decode().splitlines()
  319. packets = output[2].lstrip().split()[0]
  320. self.assertEquals(packets, '0', 'Some packet hit the INPUT rule')
  321. def test_100_late_xldevd_startup(self):
  322. '''Regression test for #1990
  323. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  324. '''
  325. # Simulater late xl devd startup
  326. cmd = "systemctl stop xendriverdomain"
  327. if self.run_cmd(self.testnetvm, cmd) != 0:
  328. self.fail("Command '%s' failed" % cmd)
  329. self.loop.run_until_complete(self.testvm1.start())
  330. cmd = "systemctl start xendriverdomain"
  331. if self.run_cmd(self.testnetvm, cmd) != 0:
  332. self.fail("Command '%s' failed" % cmd)
  333. # let it initialize the interface(s)
  334. time.sleep(1)
  335. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  336. def test_110_dynamic_attach(self):
  337. self.testvm1.netvm = None
  338. self.loop.run_until_complete(self.testvm1.start())
  339. self.testvm1.netvm = self.testnetvm
  340. # wait for it to settle down
  341. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  342. 'udevadm settle'))
  343. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  344. def test_111_dynamic_detach_attach(self):
  345. self.loop.run_until_complete(self.testvm1.start())
  346. self.testvm1.netvm = None
  347. # wait for it to settle down
  348. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  349. 'udevadm settle'))
  350. self.testvm1.netvm = self.testnetvm
  351. # wait for it to settle down
  352. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  353. 'udevadm settle'))
  354. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  355. def test_112_reattach_after_provider_shutdown(self):
  356. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  357. name=self.make_vm_name('proxy'),
  358. label='red')
  359. self.proxy.provides_network = True
  360. self.proxy.netvm = self.testnetvm
  361. self.loop.run_until_complete(self.proxy.create_on_disk())
  362. self.testvm1.netvm = self.proxy
  363. self.loop.run_until_complete(self.testvm1.start())
  364. self.loop.run_until_complete(self.proxy.shutdown(force=True, wait=True))
  365. self.loop.run_until_complete(self.proxy.start())
  366. # wait for it to settle down
  367. self.loop.run_until_complete(self.wait_for_session(self.proxy))
  368. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  369. def test_113_reattach_after_provider_kill(self):
  370. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  371. name=self.make_vm_name('proxy'),
  372. label='red')
  373. self.proxy.provides_network = True
  374. self.proxy.netvm = self.testnetvm
  375. self.loop.run_until_complete(self.proxy.create_on_disk())
  376. self.testvm1.netvm = self.proxy
  377. self.loop.run_until_complete(self.testvm1.start())
  378. self.loop.run_until_complete(self.proxy.kill())
  379. self.loop.run_until_complete(self.proxy.start())
  380. # wait for it to settle down
  381. self.loop.run_until_complete(self.wait_for_session(self.proxy))
  382. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  383. def test_114_reattach_after_provider_crash(self):
  384. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  385. name=self.make_vm_name('proxy'),
  386. label='red')
  387. self.proxy.provides_network = True
  388. self.proxy.netvm = self.testnetvm
  389. self.loop.run_until_complete(self.proxy.create_on_disk())
  390. self.testvm1.netvm = self.proxy
  391. self.loop.run_until_complete(self.testvm1.start())
  392. p = self.loop.run_until_complete(self.proxy.run(
  393. 'echo c > /proc/sysrq-trigger', user='root'))
  394. self.loop.run_until_complete(p.wait())
  395. timeout = 10
  396. while self.proxy.is_running():
  397. self.loop.run_until_complete(asyncio.sleep(1))
  398. timeout -= 1
  399. self.assertGreater(timeout, 0,
  400. 'timeout waiting for crash cleanup')
  401. self.loop.run_until_complete(self.proxy.start())
  402. # wait for it to settle down
  403. self.loop.run_until_complete(self.wait_for_session(self.proxy))
  404. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  405. def test_200_fake_ip_simple(self):
  406. '''Test hiding VM real IP
  407. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  408. '''
  409. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  410. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  411. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  412. self.app.save()
  413. self.loop.run_until_complete(self.testvm1.start())
  414. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  415. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  416. try:
  417. (output, _) = self.loop.run_until_complete(
  418. self.testvm1.run_for_stdio(
  419. 'ip addr show dev eth0', user='root'))
  420. except subprocess.CalledProcessError:
  421. self.fail('ip addr show dev eth0 failed')
  422. output = output.decode()
  423. self.assertIn('192.168.1.128', output)
  424. self.assertNotIn(str(self.testvm1.ip), output)
  425. try:
  426. (output, _) = self.loop.run_until_complete(
  427. self.testvm1.run_for_stdio('ip route show', user='root'))
  428. except subprocess.CalledProcessError:
  429. self.fail('ip route show failed')
  430. output = output.decode()
  431. self.assertIn('192.168.1.1', output)
  432. self.assertNotIn(str(self.testvm1.netvm.ip), output)
  433. def test_201_fake_ip_without_gw(self):
  434. '''Test hiding VM real IP
  435. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  436. '''
  437. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  438. self.app.save()
  439. self.loop.run_until_complete(self.testvm1.start())
  440. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  441. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  442. try:
  443. (output, _) = self.loop.run_until_complete(
  444. self.testvm1.run_for_stdio('ip addr show dev eth0',
  445. user='root'))
  446. except subprocess.CalledProcessError:
  447. self.fail('ip addr show dev eth0 failed')
  448. output = output.decode()
  449. self.assertIn('192.168.1.128', output)
  450. self.assertNotIn(str(self.testvm1.ip), output)
  451. def test_202_fake_ip_firewall(self):
  452. '''Test hiding VM real IP, firewall
  453. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  454. '''
  455. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  456. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  457. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  458. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  459. name=self.make_vm_name('proxy'),
  460. label='red')
  461. self.proxy.provides_network = True
  462. self.loop.run_until_complete(self.proxy.create_on_disk())
  463. self.proxy.netvm = self.testnetvm
  464. self.testvm1.netvm = self.proxy
  465. self.app.save()
  466. # block all but ICMP and DNS
  467. self.testvm1.firewall.rules = [
  468. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  469. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  470. ]
  471. self.testvm1.firewall.save()
  472. self.loop.run_until_complete(self.testvm1.start())
  473. self.assertTrue(self.proxy.is_running())
  474. server = self.loop.run_until_complete(self.testnetvm.run(
  475. 'socat TCP-LISTEN:1234,fork EXEC:/bin/uname'))
  476. try:
  477. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  478. "Ping by IP from ProxyVM failed")
  479. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  480. "Ping by name from ProxyVM failed")
  481. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  482. "Ping by IP should be allowed")
  483. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  484. "Ping by name should be allowed")
  485. client_cmd = "socat TCP:{}:1234 -".format(self.test_ip)
  486. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  487. "TCP connection should be blocked")
  488. finally:
  489. server.terminate()
  490. self.loop.run_until_complete(server.wait())
  491. def test_203_fake_ip_inter_vm_allow(self):
  492. '''Access VM with "fake IP" from other VM (when firewall allows)
  493. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  494. '''
  495. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  496. name=self.make_vm_name('proxy'),
  497. label='red')
  498. self.loop.run_until_complete(self.proxy.create_on_disk())
  499. self.proxy.provides_network = True
  500. self.proxy.netvm = self.testnetvm
  501. self.testvm1.netvm = self.proxy
  502. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  503. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  504. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  505. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  506. name=self.make_vm_name('vm2'),
  507. label='red')
  508. self.loop.run_until_complete(self.testvm2.create_on_disk())
  509. self.testvm2.netvm = self.proxy
  510. self.app.save()
  511. self.loop.run_until_complete(self.testvm1.start())
  512. self.loop.run_until_complete(self.testvm2.start())
  513. cmd = 'iptables -I FORWARD -s {} -d {} -j ACCEPT'.format(
  514. self.testvm2.ip, self.testvm1.ip)
  515. try:
  516. self.loop.run_until_complete(self.proxy.run_for_stdio(
  517. cmd, user='root'))
  518. except subprocess.CalledProcessError as e:
  519. raise AssertionError(
  520. '{} failed with: {}'.format(cmd, e.returncode)) from None
  521. try:
  522. cmd = 'iptables -I INPUT -s {} -j ACCEPT'.format(self.testvm2.ip)
  523. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  524. cmd, user='root'))
  525. except subprocess.CalledProcessError as e:
  526. raise AssertionError(
  527. '{} failed with: {}'.format(cmd, e.returncode)) from None
  528. self.assertEqual(self.run_cmd(self.testvm2,
  529. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  530. try:
  531. cmd = 'iptables -nvxL INPUT | grep {}'.format(self.testvm2.ip)
  532. (stdout, _) = self.loop.run_until_complete(
  533. self.testvm1.run_for_stdio(cmd, user='root'))
  534. except subprocess.CalledProcessError as e:
  535. raise AssertionError(
  536. '{} failed with {}'.format(cmd, e.returncode)) from None
  537. self.assertNotEqual(stdout.decode().split()[0], '0',
  538. 'Packets didn\'t managed to the VM')
  539. def test_204_fake_ip_proxy(self):
  540. '''Test hiding VM real IP
  541. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  542. '''
  543. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  544. name=self.make_vm_name('proxy'),
  545. label='red')
  546. self.loop.run_until_complete(self.proxy.create_on_disk())
  547. self.proxy.provides_network = True
  548. self.proxy.netvm = self.testnetvm
  549. self.proxy.features['net.fake-ip'] = '192.168.1.128'
  550. self.proxy.features['net.fake-gateway'] = '192.168.1.1'
  551. self.proxy.features['net.fake-netmask'] = '255.255.255.0'
  552. self.testvm1.netvm = self.proxy
  553. self.app.save()
  554. self.loop.run_until_complete(self.testvm1.start())
  555. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0)
  556. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0)
  557. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  558. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  559. try:
  560. (output, _) = self.loop.run_until_complete(
  561. self.proxy.run_for_stdio(
  562. 'ip addr show dev eth0', user='root'))
  563. except subprocess.CalledProcessError:
  564. self.fail('ip addr show dev eth0 failed')
  565. output = output.decode()
  566. self.assertIn('192.168.1.128', output)
  567. self.assertNotIn(str(self.testvm1.ip), output)
  568. try:
  569. (output, _) = self.loop.run_until_complete(
  570. self.proxy.run_for_stdio(
  571. 'ip route show', user='root'))
  572. except subprocess.CalledProcessError:
  573. self.fail('ip route show failed')
  574. output = output.decode()
  575. self.assertIn('192.168.1.1', output)
  576. self.assertNotIn(str(self.testvm1.netvm.ip), output)
  577. try:
  578. (output, _) = self.loop.run_until_complete(
  579. self.testvm1.run_for_stdio(
  580. 'ip addr show dev eth0', user='root'))
  581. except subprocess.CalledProcessError:
  582. self.fail('ip addr show dev eth0 failed')
  583. output = output.decode()
  584. self.assertNotIn('192.168.1.128', output)
  585. self.assertIn(str(self.testvm1.ip), output)
  586. try:
  587. (output, _) = self.loop.run_until_complete(
  588. self.testvm1.run_for_stdio(
  589. 'ip route show', user='root'))
  590. except subprocess.CalledProcessError:
  591. self.fail('ip route show failed')
  592. output = output.decode()
  593. self.assertIn('192.168.1.128', output)
  594. self.assertNotIn(str(self.proxy.ip), output)
  595. def test_210_custom_ip_simple(self):
  596. '''Custom AppVM IP
  597. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  598. '''
  599. self.testvm1.ip = '192.168.1.1'
  600. self.app.save()
  601. self.loop.run_until_complete(self.testvm1.start())
  602. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  603. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  604. def test_211_custom_ip_proxy(self):
  605. '''Custom ProxyVM IP
  606. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  607. '''
  608. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  609. name=self.make_vm_name('proxy'),
  610. label='red')
  611. self.loop.run_until_complete(self.proxy.create_on_disk())
  612. self.proxy.provides_network = True
  613. self.proxy.netvm = self.testnetvm
  614. self.proxy.ip = '192.168.1.1'
  615. self.testvm1.netvm = self.proxy
  616. self.app.save()
  617. self.loop.run_until_complete(self.testvm1.start())
  618. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  619. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  620. def test_212_custom_ip_firewall(self):
  621. '''Custom VM IP and firewall
  622. :type self: qubes.tests.SystemTestCase | VMNetworkingMixin
  623. '''
  624. self.testvm1.ip = '192.168.1.1'
  625. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  626. name=self.make_vm_name('proxy'),
  627. label='red')
  628. self.proxy.provides_network = True
  629. self.loop.run_until_complete(self.proxy.create_on_disk())
  630. self.proxy.netvm = self.testnetvm
  631. self.testvm1.netvm = self.proxy
  632. self.app.save()
  633. # block all but ICMP and DNS
  634. self.testvm1.firewall.rules = [
  635. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  636. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  637. ]
  638. self.testvm1.firewall.save()
  639. self.loop.run_until_complete(self.testvm1.start())
  640. self.assertTrue(self.proxy.is_running())
  641. server = self.loop.run_until_complete(self.testnetvm.run(
  642. 'socat TCP-LISTEN:1234,fork EXEC:/bin/uname'))
  643. try:
  644. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  645. "Ping by IP from ProxyVM failed")
  646. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  647. "Ping by name from ProxyVM failed")
  648. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  649. "Ping by IP should be allowed")
  650. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  651. "Ping by name should be allowed")
  652. client_cmd = "socat TCP:{}:1234 -".format(self.test_ip)
  653. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  654. "TCP connection should be blocked")
  655. finally:
  656. server.terminate()
  657. self.loop.run_until_complete(server.wait())
  658. # noinspection PyAttributeOutsideInit,PyPep8Naming
  659. class VmIPv6NetworkingMixin(VmNetworkingMixin):
  660. test_ip6 = '2000:abcd::1'
  661. ping6_cmd = 'ping6 -W 1 -n -c 1 {target}'
  662. def setUp(self):
  663. super(VmIPv6NetworkingMixin, self).setUp()
  664. self.ping6_ip = self.ping6_cmd.format(target=self.test_ip6)
  665. self.ping6_name = self.ping6_cmd.format(target=self.test_name)
  666. def configure_netvm(self):
  667. '''
  668. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  669. '''
  670. self.testnetvm.features['ipv6'] = True
  671. super(VmIPv6NetworkingMixin, self).configure_netvm()
  672. def run_netvm_cmd(cmd):
  673. try:
  674. self.loop.run_until_complete(
  675. self.testnetvm.run_for_stdio(cmd, user='root'))
  676. except subprocess.CalledProcessError as e:
  677. self.fail("Command '%s' failed: %s%s" %
  678. (cmd, e.stdout.decode(), e.stderr.decode()))
  679. run_netvm_cmd("ip addr add {}/128 dev test0".format(self.test_ip6))
  680. run_netvm_cmd(
  681. "ip6tables -I INPUT -d {} -j ACCEPT".format(self.test_ip6))
  682. # ignore failure
  683. self.run_cmd(self.testnetvm, "pkill dnsmasq")
  684. run_netvm_cmd(
  685. "dnsmasq -a {ip} -A /{name}/{ip} -A /{name}/{ip6} -i test0 -z".
  686. format(ip=self.test_ip, ip6=self.test_ip6, name=self.test_name))
  687. def test_500_ipv6_simple_networking(self):
  688. '''
  689. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  690. '''
  691. self.loop.run_until_complete(self.testvm1.start())
  692. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  693. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  694. def test_510_ipv6_simple_proxyvm(self):
  695. '''
  696. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  697. '''
  698. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  699. name=self.make_vm_name('proxy'),
  700. label='red')
  701. self.proxy.provides_network = True
  702. self.proxy.netvm = self.testnetvm
  703. self.loop.run_until_complete(self.proxy.create_on_disk())
  704. self.testvm1.netvm = self.proxy
  705. self.app.save()
  706. self.loop.run_until_complete(self.testvm1.start())
  707. self.assertTrue(self.proxy.is_running())
  708. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  709. "Ping by IP from ProxyVM failed")
  710. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  711. "Ping by name from ProxyVM failed")
  712. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  713. "Ping by IP from AppVM failed")
  714. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  715. "Ping by IP from AppVM failed")
  716. @qubes.tests.expectedFailureIfTemplate('debian-7')
  717. @unittest.skipUnless(spawn.find_executable('xdotool'),
  718. "xdotool not installed")
  719. def test_520_ipv6_simple_proxyvm_nm(self):
  720. '''
  721. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  722. '''
  723. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  724. name=self.make_vm_name('proxy'),
  725. label='red')
  726. self.proxy.provides_network = True
  727. self.loop.run_until_complete(self.proxy.create_on_disk())
  728. self.proxy.netvm = self.testnetvm
  729. self.proxy.features['service.network-manager'] = True
  730. self.testvm1.netvm = self.proxy
  731. self.app.save()
  732. self.loop.run_until_complete(self.testvm1.start())
  733. self.assertTrue(self.proxy.is_running())
  734. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  735. "Ping by IP failed")
  736. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  737. "Ping by name failed")
  738. # reconnect to make sure that device was configured by NM
  739. self.assertEqual(
  740. self.run_cmd(self.proxy, "nmcli device disconnect eth0",
  741. user="user"),
  742. 0, "Failed to disconnect eth0 using nmcli")
  743. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  744. "Network should be disabled, but apparently it isn't")
  745. self.assertEqual(
  746. self.run_cmd(self.proxy,
  747. 'nmcli connection up "VM uplink eth0" ifname eth0',
  748. user="user"),
  749. 0, "Failed to connect eth0 using nmcli")
  750. self.assertEqual(self.run_cmd(self.proxy, "nm-online",
  751. user="user"), 0,
  752. "Failed to wait for NM connection")
  753. # wait for duplicate-address-detection to complete - by default it has
  754. # 1s timeout
  755. time.sleep(2)
  756. # check for nm-applet presence
  757. self.assertEqual(subprocess.call([
  758. 'xdotool', 'search', '--class', '{}:nm-applet'.format(
  759. self.proxy.name)],
  760. stdout=subprocess.DEVNULL), 0, "nm-applet window not found")
  761. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  762. "Ping by IP failed (after NM reconnection")
  763. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  764. "Ping by name failed (after NM reconnection)")
  765. def test_530_ipv6_firewallvm_firewall(self):
  766. '''
  767. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  768. '''
  769. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  770. name=self.make_vm_name('proxy'),
  771. label='red')
  772. self.proxy.provides_network = True
  773. self.loop.run_until_complete(self.proxy.create_on_disk())
  774. self.proxy.netvm = self.testnetvm
  775. self.testvm1.netvm = self.proxy
  776. self.app.save()
  777. # block all for first
  778. self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')]
  779. self.testvm1.firewall.save()
  780. self.loop.run_until_complete(self.testvm1.start())
  781. self.assertTrue(self.proxy.is_running())
  782. server = self.loop.run_until_complete(self.testnetvm.run(
  783. 'socat TCP6-LISTEN:1234,fork EXEC:/bin/uname'))
  784. try:
  785. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  786. "Ping by IP from ProxyVM failed")
  787. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  788. "Ping by name from ProxyVM failed")
  789. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  790. "Ping by IP should be blocked")
  791. client6_cmd = "socat TCP:[{}]:1234 -".format(self.test_ip6)
  792. client4_cmd = "socat TCP:{}:1234 -".format(self.test_ip)
  793. self.assertNotEqual(self.run_cmd(self.testvm1, client6_cmd), 0,
  794. "TCP connection should be blocked")
  795. # block all except ICMP
  796. self.testvm1.firewall.rules = [(
  797. qubes.firewall.Rule(None, action='accept', proto='icmp')
  798. )]
  799. self.testvm1.firewall.save()
  800. # Ugly hack b/c there is no feedback when the rules are actually
  801. # applied
  802. time.sleep(3)
  803. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  804. "Ping by IP failed (should be allowed now)")
  805. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  806. "Ping by name should be blocked")
  807. # all TCP still blocked
  808. self.testvm1.firewall.rules = [
  809. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  810. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  811. ]
  812. self.testvm1.firewall.save()
  813. # Ugly hack b/c there is no feedback when the rules are actually
  814. # applied
  815. time.sleep(3)
  816. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  817. "Ping by name failed (should be allowed now)")
  818. self.assertNotEqual(self.run_cmd(self.testvm1, client6_cmd), 0,
  819. "TCP connection should be blocked")
  820. # block all except target
  821. self.testvm1.firewall.rules = [
  822. qubes.firewall.Rule(None, action='accept',
  823. dsthost=self.test_ip6,
  824. proto='tcp', dstports=1234),
  825. ]
  826. self.testvm1.firewall.save()
  827. # Ugly hack b/c there is no feedback when the rules are actually
  828. # applied
  829. time.sleep(3)
  830. self.assertEqual(self.run_cmd(self.testvm1, client6_cmd), 0,
  831. "TCP connection failed (should be allowed now)")
  832. # block all except target - by name
  833. self.testvm1.firewall.rules = [
  834. qubes.firewall.Rule(None, action='accept',
  835. dsthost=self.test_name,
  836. proto='tcp', dstports=1234),
  837. ]
  838. self.testvm1.firewall.save()
  839. # Ugly hack b/c there is no feedback when the rules are actually
  840. # applied
  841. time.sleep(3)
  842. self.assertEqual(self.run_cmd(self.testvm1, client6_cmd), 0,
  843. "TCP (IPv6) connection failed (should be allowed now)")
  844. self.assertEqual(self.run_cmd(self.testvm1, client4_cmd),
  845. 0,
  846. "TCP (IPv4) connection failed (should be allowed now)")
  847. # allow all except target
  848. self.testvm1.firewall.rules = [
  849. qubes.firewall.Rule(None, action='drop', dsthost=self.test_ip6,
  850. proto='tcp', dstports=1234),
  851. qubes.firewall.Rule(action='accept'),
  852. ]
  853. self.testvm1.firewall.save()
  854. # Ugly hack b/c there is no feedback when the rules are actually
  855. # applied
  856. time.sleep(3)
  857. self.assertNotEqual(self.run_cmd(self.testvm1, client6_cmd), 0,
  858. "TCP connection should be blocked")
  859. finally:
  860. server.terminate()
  861. self.loop.run_until_complete(server.wait())
  862. def test_540_ipv6_inter_vm(self):
  863. '''
  864. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  865. '''
  866. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  867. name=self.make_vm_name('proxy'),
  868. label='red')
  869. self.loop.run_until_complete(self.proxy.create_on_disk())
  870. self.proxy.provides_network = True
  871. self.proxy.netvm = self.testnetvm
  872. self.testvm1.netvm = self.proxy
  873. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  874. name=self.make_vm_name('vm2'),
  875. label='red')
  876. self.loop.run_until_complete(self.testvm2.create_on_disk())
  877. self.testvm2.netvm = self.proxy
  878. self.app.save()
  879. self.loop.run_until_complete(asyncio.wait([
  880. self.testvm1.start(),
  881. self.testvm2.start()]))
  882. self.assertNotEqual(self.run_cmd(self.testvm1,
  883. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  884. self.testvm2.netvm = self.testnetvm
  885. self.assertNotEqual(self.run_cmd(self.testvm1,
  886. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  887. self.assertNotEqual(self.run_cmd(self.testvm2,
  888. self.ping_cmd.format(target=self.testvm1.ip6)), 0)
  889. self.testvm1.netvm = self.testnetvm
  890. self.assertNotEqual(self.run_cmd(self.testvm1,
  891. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  892. self.assertNotEqual(self.run_cmd(self.testvm2,
  893. self.ping_cmd.format(target=self.testvm1.ip6)), 0)
  894. def test_550_ipv6_spoof_ip(self):
  895. '''Test if VM IP spoofing is blocked
  896. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  897. '''
  898. self.loop.run_until_complete(self.testvm1.start())
  899. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  900. # add a simple rule counting packets
  901. self.assertEqual(self.run_cmd(self.testnetvm,
  902. 'ip6tables -I INPUT -i vif+ ! -s {} -p icmpv6 -j LOG'.format(
  903. self.testvm1.ip6)), 0)
  904. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  905. 'ip -6 addr flush dev eth0 && '
  906. 'ip -6 addr add {}/128 dev eth0 && '
  907. 'ip -6 route add default via {} dev eth0'.format(
  908. str(self.testvm1.visible_ip6) + '1',
  909. str(self.testvm1.visible_gateway6)),
  910. user='root'))
  911. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  912. "Spoofed ping should be blocked")
  913. try:
  914. (output, _) = self.loop.run_until_complete(
  915. self.testnetvm.run_for_stdio('ip6tables -nxvL INPUT',
  916. user='root'))
  917. except subprocess.CalledProcessError:
  918. self.fail('ip6tables -nxvL INPUT failed')
  919. output = output.decode().splitlines()
  920. packets = output[2].lstrip().split()[0]
  921. self.assertEquals(packets, '0', 'Some packet hit the INPUT rule')
  922. def test_710_ipv6_custom_ip_simple(self):
  923. '''Custom AppVM IP
  924. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  925. '''
  926. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  927. self.app.save()
  928. self.loop.run_until_complete(self.testvm1.start())
  929. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  930. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  931. def test_711_ipv6_custom_ip_proxy(self):
  932. '''Custom ProxyVM IP
  933. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  934. '''
  935. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  936. name=self.make_vm_name('proxy'),
  937. label='red')
  938. self.loop.run_until_complete(self.proxy.create_on_disk())
  939. self.proxy.provides_network = True
  940. self.proxy.netvm = self.testnetvm
  941. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  942. self.testvm1.netvm = self.proxy
  943. self.app.save()
  944. self.loop.run_until_complete(self.testvm1.start())
  945. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  946. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  947. def test_712_ipv6_custom_ip_firewall(self):
  948. '''Custom VM IP and firewall
  949. :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin
  950. '''
  951. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  952. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  953. name=self.make_vm_name('proxy'),
  954. label='red')
  955. self.proxy.provides_network = True
  956. self.loop.run_until_complete(self.proxy.create_on_disk())
  957. self.proxy.netvm = self.testnetvm
  958. self.testvm1.netvm = self.proxy
  959. self.app.save()
  960. # block all but ICMP and DNS
  961. self.testvm1.firewall.rules = [
  962. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  963. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  964. ]
  965. self.testvm1.firewall.save()
  966. self.loop.run_until_complete(self.testvm1.start())
  967. self.assertTrue(self.proxy.is_running())
  968. server = self.loop.run_until_complete(self.testnetvm.run(
  969. 'socat TCP6-LISTEN:1234,fork EXEC:/bin/uname'))
  970. try:
  971. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  972. "Ping by IP from ProxyVM failed")
  973. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  974. "Ping by name from ProxyVM failed")
  975. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  976. "Ping by IP should be allowed")
  977. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  978. "Ping by name should be allowed")
  979. client_cmd = "socat TCP:[{}]:1234 -".format(self.test_ip6)
  980. self.assertNotEqual(self.run_cmd(self.testvm1, client_cmd), 0,
  981. "TCP connection should be blocked")
  982. finally:
  983. server.terminate()
  984. self.loop.run_until_complete(server.wait())
  985. # noinspection PyAttributeOutsideInit,PyPep8Naming
  986. class VmUpdatesMixin(object):
  987. """
  988. Tests for VM updates
  989. """
  990. # filled by load_tests
  991. template = None
  992. # made this way to work also when no package build tools are installed
  993. """
  994. $ cat test-pkg.spec:
  995. Name: test-pkg
  996. Version: 1.0
  997. Release: 1%{?dist}
  998. Summary: Test package
  999. Group: System
  1000. License: GPL
  1001. URL: http://example.com/
  1002. %description
  1003. Test package
  1004. %files
  1005. %changelog
  1006. $ rpmbuild -bb test-pkg.spec
  1007. $ cat test-pkg-1.0-1.fc21.x86_64.rpm | gzip | base64
  1008. $ cat test-pkg-1.1-1.fc21.x86_64.rpm | gzip | base64
  1009. """
  1010. RPM_PACKAGE_GZIP_BASE64 = [(
  1011. b"H4sIAPzRLlYAA+2Y728URRjHn7ueUCkERKJVJDnTxLSxs7293o8WOER6ljYYrtKCLUSa3"
  1012. b"bnZ64bd22VmTq8nr4wJbwxvjNHIG0x8oTHGGCHB8AcYE1/0lS80GgmQFCJU3wgB4ZjdfZ"
  1013. b"q2xDe8NNlvMjfzmeeZH7tPbl98b35169cOUEpIJiTxT9SIrmVUs2hWh8dUAp54dOrM14s"
  1014. b"JHK4D2DKl+j2qrVfjsuq3qEWbohjuAB2Lqk+p1o/8Z5QPmSi/YwnjezH+F8bLQZjqllW0"
  1015. b"hvODRmFIL5hFk9JMXi/mi5ZuDleNwSEzP5wtmLnouNQnm3/6fndz7FLt9M/Hruj37gav4"
  1016. b"tTjPnasWLFixYoVK1asWLFixYoV63+p0KNot9vnIPQc1vgYOwCSgXfxCoS+QzKHOVXVOj"
  1017. b"Fn2ccIfI0k8nXkLuQbyJthxed4UrVnkG8i9yDfgsj3yCAv4foc8t+w1hf5B+Nl5Du43xj"
  1018. b"yvxivIN9HpsgPkO2IU9uQfeRn8Xk/iJ4x1Y3nfxH1qecwfhH5+YgT25F7o/0SRdxvOppP"
  1019. b"7MX9ZjB/DNnE/OOYX404uRGZIT+FbCFvQ3aQ8f0+/WF0XjJ8nyOw7H+BrmUA/a8pNZf2D"
  1020. b"XrCqLG1cERbWHI8ajhznpBY9P0Tr8PkvJDMhTkp/Z0DA6xpuL7DNOq5A+DY9UYTmkOF2U"
  1021. b"IO/sNt0wSnGvfdlZssD3rVIlLI9UUX37C6qXzHNntHPNfnTAhWHbUddtBwmegDjAUzZbu"
  1022. b"m9lqZmzDmHc8Ik8WY8Tab4Myym4+Gx8V0qw8GtYyWIzrktEJwV9UHv3ktG471rAqHTmFQ"
  1023. b"685V5uGqIalk06SWJr7tszR503Ac9cs493jJ8rhrSCIYbXBbzqt5v5+UZ0crh6bGR2dmJ"
  1024. b"yuHD428VlLLLdakzJe2VxcKhFSFID73JKPS40RI7tXVCcQ3uOGWhPCJ2bAspiJ2i5Vy6n"
  1025. b"jOqMerpEYpEe/Yks4xkU4Tt6BirmzUWanG6ozbFKhve9BsQRaLRTirzqk7hgUktXojKnf"
  1026. b"n8jeg3X4QepP3i63po6oml+9t/CwJLya2Bn/ei6f7/4B3Ycdb0L3pt5Q5mNz16rWJ9fLk"
  1027. b"vvOff/nxS7//8O2P2gvt7nDDnoV9L1du9N4+ucjl9u/8+a7dC5Nnvjlv9Ox5r+v9Cy0NE"
  1028. b"m+c6rv60S/dZw98Gn6MNswcfQiWUvg3wBUAAA=="
  1029. ), (
  1030. b"H4sIAMY1B1wCA+2Y72scRRjH537U1MZorKLRVjgJSIKdvf11u3dq0jZJ0wRLL+1VvBRrn"
  1031. b"J2dvVu6t7vd3bN3sS9ECr6RIkgR9JXQF5aiIk1L/gJF8EVe+KqiKLQQi03tmypojXO3zz"
  1032. b"Vp8YW+3y/MPvOZ55lnZthhXjw3Lqx9n0FcqYiFEfaP17AkSLxZVJbQ/1QKbbl/6Mxnqyn"
  1033. b"o9iE0uMTtOPTPcTvIJw1w+8DdDCj1KPBozJlVbrO8OcC/xvORH8/P3AT/2+D/DfynuTtH"
  1034. b"FKtANKNo6KpKJIs3jSkl3VIkvSAWSiZTlCL3akhXZCKKKmPcaRRJqURFS2MlSTMsgyqMz"
  1035. b"6RUp8SQFcmixZJpMlEWi0w1da3Eu3p3+1uW1saPHfpWOSvNXtruDVx4+A0+eAolSpQoUa"
  1036. b"JEiRIlSpQoUaJEiRJBTWR9ff191K1p3FM3ySGUEbndjbp1jUwOYkzetkJMr07SqZukgX8"
  1037. b"B7ge+DvwI2qijPMjbE8A3gIeB11BcVxGBb8J8FfgW+PcA3wb/FPAfkG8G+C/wl4HvAFPg"
  1038. b"v4HtmLOPA/vAT8J534vPmB2C9T+NbfYp8C8DPx1zagfwSJwvpUO+ajye2gP55iF+BtiA+"
  1039. b"Nch3ow5/TkwA74IbAFfBnaAl2N+7IN4vfQK8Ffg/w74arx++grwtTg+s7PDk6hXn0OSIC"
  1040. b"Gozx3hYzmf0OOkxu6F1/oKlx2PEqfuhRFckv1zB1ClHUasgepR5L+Qz7MWafgOE6jXyCP"
  1041. b"Hdpst1CpqC5qK/qUaKIQBFQK/sbGTXmeET8KaCgW7bZsbj3dsY2TSa/gBC0NmTtsOO0ga"
  1042. b"LBxF4OuMTNk1nmtjbI60HY90g8MZ8iabC5hlt+53z4bVxVGkCKKgYgmpgiaIXdv5FgS52"
  1043. b"5dUQY6P37kbWzcVNzd1cVnO4VoO+7bPcvhV4jj8y4LAC8YsL2iQCIeMNgM7avNxfxeeWp"
  1044. b"guHz4yOz2/UCm/cnhy35jcG99/YHZislpd2Fup7OMR5YOVHLZYizI/sj035BBG/BdhP/A"
  1045. b"iRiMvwGEUeC5fuxYw6gUmrlGKw5N2ROuMh4c+o+FYvhkGeX7wPD9/PmBmnURgcJ0EJnOZ"
  1046. b"iSmV/kM4cV3PsN04uqGp/BM1XTZW4zkCm/L9kbDt0jrfk9cMcdM9absmjojhsI3NU4eE9"
  1047. b"d4R+LG4g1qbGFHf9lBrEclwnTCs3r1iuOY2u/+jGVm4iCwiyXpJE61SkUq6RhVW0FVFpo"
  1048. b"ZZ0oiu6ppuFSxSFBXTUOQCFRmhhElFQ9XNgiyJhbv/dnf8hnaeETR4R1+sHuX37+c/H/o"
  1049. b"kjZ5Nbe88bMvv7voJvYWeOYaGBn7IGkr6xb3X5vqiExNL585/+NyPX3/5jbBzfaibcHhl"
  1050. b"4vny9ZHfT6wG0Y6Lfrv/pZXKmS+WyPD4O/2nLy0KKHXo1OjVs1eGPn75o+5DvW3+6D9jd"
  1051. b"bFaTBcAAA=="
  1052. )]
  1053. """
  1054. Minimal package generated by running dh_make on empty directory
  1055. Then cat test-pkg_1.0-1_amd64.deb | gzip | base64
  1056. Then cat test-pkg_1.1-1_amd64.deb | gzip | base64
  1057. """
  1058. DEB_PACKAGE_GZIP_BASE64 = [(
  1059. b"H4sIACTXLlYAA1O0SSxKzrDjSklNykzM003KzEssqlRQUDA0MTG1NDQwNDVTUDBQAAEIa"
  1060. b"WhgYGZioqBgogADCVxGegZcyfl5JUX5OXoliUV66VVE6DcwheuX7+ZgAAEW5rdXHb0PG4"
  1061. b"iwf5j3WfMT6zWzzMuZgoE3jjYraNzbbFKWGms0SaRw/r2SV23WZ4IdP8preM4yqf0jt95"
  1062. b"3c8qnacfNxJUkf9/w+/3X9ph2GEdgQdixrz/niHKKTnYXizf4oSC7tHOz2Zzq+/6vn8/7"
  1063. b"ezQ7c1tmi7xZ3SGJ4yzhT2dcr7V+W3zM5ZPu/56PSv4Zdok+7Yv/V/6buWaKVlFkkV58S"
  1064. b"N3GmLgnqzRmeZ3V3ymmurS5fGa85/LNx1bpZMin3S6dvXKqydp3ubP1vmyarJZb/qSh62"
  1065. b"C8oIdxqm/BtvkGDza+On/Vfv2py7/0LV7VH+qR6a+bkKUbHXt5/SG187d+nps1a5PJfMO"
  1066. b"i11dWcUe1HjwaW3Q5RHXn9LmcHy+tW9YcKf0768XVB1t3R0bKrzs5t9P+6r7rZ99svH10"
  1067. b"+Q6F/o8tf1fO/32y+fWa14eifd+WxUy0jcxYH7N9/tUvmnUZL74pW32qLeuRU+ZwYGASa"
  1068. b"GBgUWBgxM90ayy3VdmykkGDgYErJbEkERydFVWQmCMQo8aWZvAY/WteFRHFwMCYqXTPjI"
  1069. b"lBkVEMGLsl+k8XP1D/z+gXyyDOvUemlnHqAVkvu0rRQ2fUFodkN3mtU9uwhqk8V+TqPEE"
  1070. b"Nc7fzoQ4n71lqRs/7kbbT0+qOZuKH4r8mjzsc1k/YkCHN8Pjg48fbpE+teHa96LNcfu0V"
  1071. b"5n2/Z2xa2KDvaCOx8cqBFxc514uZ3TmadXS+6cpzU7wSzq5SWfapJOD9n6wLXSwtlgxZh"
  1072. b"xITzWW7buhx/bb291RcVlEfeC9K5hlrqunSzIMSZT7/Nqgc/qMvMNW227WI8ezB8mVuZh"
  1073. b"0hERJSvysfburr4Dx0I9BW57UwR4+e1gxu49PcEt8sbK18Xpvt//Hj5UYm+Zc25q+T4xl"
  1074. b"rJvxfVnh80oadq57OZxPaU1bbztv1yF365W4t45Yr+XrFzov237GVY1Zgf7NvE4+W2SuR"
  1075. b"lQtLauR1TQ/mbOiIONYya6tU1jPGpWfk/i1+ttiXe3ZO14n0YOWggndznjGlGLyfVbBC6"
  1076. b"MRP5aMM7aCco/s7sZqB8RlTQwADw8rnuT/sDHi7mUASjJFRAAbWwNLiAwAA"
  1077. ), (
  1078. b"H4sIAL05B1wCA1O0SSxKzrDjSklNykzM003KzEssqlRQUDA0NTG2NDc3NjdTUDBQAAEIa"
  1079. b"WhgYGZioqBgogADCVxGegZcyfl5JUX5OXoliUV66VVE6De3gOuX7+ZgAAEW5rdXzmbdMR"
  1080. b"BgSJj/VeQzQ+ztT/W+EVEnFraKOTlXh6+JXB8RbTRpzgWb2qdLX0+RmTRZcYlyxJutJsk"
  1081. b"/pfsfq9yqWZJ4JVVS97jBPPnz1yviluw51b0q4tnrWemCU2a/17mTUBYX0XBC6nH8rvvZ"
  1082. b"n/WP7nu40+Jlz7drPNLvCjULQkXOv677OV9s4bPsv5+tvCzPG8s57no479qV/5V/813Kh"
  1083. b"Wy3Pbj4827Jq5v6W/wk7zL1/+zbfH6btVb/3Pm5EapukaJvdgfcape/JZZWe+mZ4+Grby"
  1084. b"7UTaroPzyv9urC1W2MT9+F2bZtWJOyXfGo5dv7DGXJUzee+p930Od0j8QNceNHJffOTr2"
  1085. b"kOJe93mWG+nPdLsG6fz++MV5h1OGr0N9yf3N2ydzQ5x/E9Aw/s9xzmOpULnKtsSZqc/rr"
  1086. b"RQdf/Lu/ckKE9xU5VRuNehbzTr6789a+P2lt2zk5cFqe3N2289+j/hfH2X39/+nvc5vTW"
  1087. b"a/+83pvWqY3e93JWYsmup693HzCOPBk0LI9O7PtiqawN9y8eaTV75DLLL2dNWqTLsTsOn"
  1088. b"7wy0fTe5oLH//7eNf89Co3dRUHJmLRh20s/xhYJkoeYdBgYEhJLEkEJ4uKKkgKIJQyjI3"
  1089. b"gKeOveVVEFAMDY6bSPTMmBkVGMWAqKdF/uviB+n/GwlgGce49MrWMUw/IetlVih46o7Y4"
  1090. b"0uZe/t9lt85aMUrdWhjueTHRd1nr1uK830feH74vcPKU2pkbP4SZnta5PhC9dfPTqvv7f"
  1091. b"n068XRDRDzLuv8Oa5p1L+02ZN127vp6mzSzzFqpLkmbwyl131J1xW58YlcxXSWs0PTbpT"
  1092. b"z28ZUnE/e+NN93weAd40a/zzJ7+Re/v+R7+f3VBVFJCyZsv523ySJ12t7Nt5b8uBu8zuJ"
  1093. b"2Laer//nZCkbXlxtYXvvA8+VSVsCRpo8BawtftKWyZBjkWa6/0X7qXfbF9reH/ro6S63Y"
  1094. b"rCj8t8cltPIOj9H/8LyIxj6bMsZVVtu+ngj6MCNV5JXhOs07RXWxrb3xsqJMDRksx/5bO"
  1095. b"bNtevXz2cdpzzI19Roede4NXxAyK9Dlrtp8JtELLNPWbBe9HfJlj1Hiv69erIFBnX/Pe1"
  1096. b"4QnzLD+p2AiTc383/P+7sW3WoxnXra49iJKJeZy7gc9Z02S57qrvWW3day501VhsbPtfK"
  1097. b"C5nyBG9qjr08E59KY1vUTGRg7mRsCGBimFa+3sTPg7WYCSTBGRgEAzEOeH04EAAA="
  1098. )]
  1099. def run_cmd(self, vm, cmd, user="root"):
  1100. '''Run a command *cmd* in a *vm* as *user*. Return its exit code.
  1101. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1102. :param qubes.vm.qubesvm.QubesVM vm: VM object to run command in
  1103. :param str cmd: command to execute
  1104. :param std user: user to execute command as
  1105. :return int: command exit code
  1106. '''
  1107. try:
  1108. self.loop.run_until_complete(vm.run_for_stdio(cmd))
  1109. except subprocess.CalledProcessError as e:
  1110. return e.returncode
  1111. return 0
  1112. def assertRunCommandReturnCode(self, vm, cmd, expected_returncode):
  1113. p = self.loop.run_until_complete(
  1114. vm.run(cmd, user='root',
  1115. stdout=subprocess.PIPE, stderr=subprocess.PIPE))
  1116. (stdout, stderr) = self.loop.run_until_complete(p.communicate())
  1117. self.assertIn(
  1118. self.loop.run_until_complete(p.wait()), expected_returncode,
  1119. '{}: {}\n{}'.format(cmd, stdout, stderr))
  1120. def setUp(self):
  1121. '''
  1122. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1123. '''
  1124. if not self.template.count('debian') and \
  1125. not self.template.count('fedora'):
  1126. self.skipTest("Template {} not supported by this test".format(
  1127. self.template))
  1128. super(VmUpdatesMixin, self).setUp()
  1129. self.update_cmd = None
  1130. if self.template.count("debian"):
  1131. self.update_cmd = "set -o pipefail; apt-get update 2>&1 | " \
  1132. "{ ! grep '^W:\|^E:'; }"
  1133. self.upgrade_cmd = "apt-get -V dist-upgrade -y"
  1134. self.install_cmd = "apt-get install -y {}"
  1135. self.install_test_cmd = "dpkg -l {}"
  1136. self.exit_code_ok = [0]
  1137. elif self.template.count("fedora"):
  1138. cmd = "yum"
  1139. try:
  1140. # assume template name in form "fedora-XX-suffix"
  1141. if int(self.template.split("-")[1]) > 21:
  1142. cmd = "dnf"
  1143. except ValueError:
  1144. pass
  1145. self.update_cmd = "{cmd} clean all; {cmd} check-update".format(
  1146. cmd=cmd)
  1147. self.upgrade_cmd = "{cmd} upgrade -y".format(cmd=cmd)
  1148. self.install_cmd = cmd + " install -y {}"
  1149. self.install_test_cmd = "rpm -q {}"
  1150. self.exit_code_ok = [0, 100]
  1151. self.init_default_template(self.template)
  1152. self.init_networking()
  1153. self.testvm1 = self.app.add_new_vm(
  1154. qubes.vm.appvm.AppVM,
  1155. name=self.make_vm_name('vm1'),
  1156. label='red')
  1157. self.loop.run_until_complete(self.testvm1.create_on_disk())
  1158. def test_000_simple_update(self):
  1159. '''
  1160. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1161. '''
  1162. self.app.save()
  1163. self.testvm1 = self.app.domains[self.testvm1.qid]
  1164. self.loop.run_until_complete(self.testvm1.start())
  1165. self.assertRunCommandReturnCode(self.testvm1,
  1166. self.update_cmd, self.exit_code_ok)
  1167. def create_repo_apt(self, version=0):
  1168. '''
  1169. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1170. '''
  1171. pkg_file_name = "test-pkg_1.{}-1_amd64.deb".format(version)
  1172. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1173. mkdir -p /tmp/apt-repo \
  1174. && cd /tmp/apt-repo \
  1175. && base64 -d | zcat > {}
  1176. '''.format(pkg_file_name),
  1177. input=self.DEB_PACKAGE_GZIP_BASE64[version]))
  1178. # do not assume dpkg-scanpackage installed
  1179. packages_path = "dists/test/main/binary-amd64/Packages"
  1180. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1181. mkdir -p /tmp/apt-repo/dists/test/main/binary-amd64 \
  1182. && cd /tmp/apt-repo \
  1183. && cat > {packages} \
  1184. && echo MD5sum: $(openssl md5 -r {pkg} | cut -f 1 -d ' ') \
  1185. >> {packages} \
  1186. && echo SHA1: $(openssl sha1 -r {pkg} | cut -f 1 -d ' ') \
  1187. >> {packages} \
  1188. && echo SHA256: $(openssl sha256 -r {pkg} | cut -f 1 -d ' ') \
  1189. >> {packages} \
  1190. && sed -i -e "s,@SIZE@,$(stat -c %s {pkg})," {packages} \
  1191. && gzip < {packages} > {packages}.gz
  1192. '''.format(pkg=pkg_file_name, packages=packages_path),
  1193. input='''\
  1194. Package: test-pkg
  1195. Version: 1.{version}-1
  1196. Architecture: amd64
  1197. Maintainer: unknown <user@host>
  1198. Installed-Size: 25
  1199. Filename: {pkg}
  1200. Size: @SIZE@
  1201. Section: unknown
  1202. Priority: optional
  1203. Description: Test package'''.format(pkg=pkg_file_name, version=version).encode(
  1204. 'utf-8')))
  1205. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1206. mkdir -p /tmp/apt-repo/dists/test \
  1207. && cd /tmp/apt-repo/dists/test \
  1208. && cat > Release \
  1209. && echo '' $(sha256sum {p} | cut -f 1 -d ' ') $(stat -c %s {p}) {p}\
  1210. >> Release \
  1211. && echo '' $(sha256sum {z} | cut -f 1 -d ' ') $(stat -c %s {z}) {z}\
  1212. >> Release
  1213. '''.format(p='main/binary-amd64/Packages',
  1214. z='main/binary-amd64/Packages.gz'),
  1215. input=b'''\
  1216. Label: Test repo
  1217. Suite: test
  1218. Codename: test
  1219. Date: Tue, 27 Oct 2015 03:22:09 UTC
  1220. Architectures: amd64
  1221. Components: main
  1222. SHA256:
  1223. '''))
  1224. def create_repo_yum(self, version=0):
  1225. '''
  1226. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1227. '''
  1228. pkg_file_name = "test-pkg-1.{}-1.fc21.x86_64.rpm".format(version)
  1229. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1230. mkdir -p /tmp/yum-repo \
  1231. && cd /tmp/yum-repo \
  1232. && base64 -d | zcat > {}
  1233. '''.format(pkg_file_name), input=self.RPM_PACKAGE_GZIP_BASE64[
  1234. version]))
  1235. # createrepo is installed by default in Fedora template
  1236. self.loop.run_until_complete(self.netvm_repo.run_for_stdio(
  1237. 'createrepo /tmp/yum-repo'))
  1238. def create_repo_and_serve(self):
  1239. '''
  1240. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1241. '''
  1242. if self.template.count("debian") or self.template.count("whonix"):
  1243. self.create_repo_apt()
  1244. self.loop.run_until_complete(self.netvm_repo.run(
  1245. 'cd /tmp/apt-repo && python -m SimpleHTTPServer 8080',
  1246. stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
  1247. elif self.template.count("fedora"):
  1248. self.create_repo_yum()
  1249. self.loop.run_until_complete(self.netvm_repo.run(
  1250. 'cd /tmp/yum-repo && python -m SimpleHTTPServer 8080',
  1251. stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
  1252. else:
  1253. # not reachable...
  1254. self.skipTest("Template {} not supported by this test".format(
  1255. self.template))
  1256. def add_update_to_repo(self):
  1257. if self.template.count("debian") or self.template.count("whonix"):
  1258. self.create_repo_apt(1)
  1259. elif self.template.count("fedora"):
  1260. self.create_repo_yum(1)
  1261. def configure_test_repo(self):
  1262. """
  1263. Configure test repository in test-vm and disable rest of them.
  1264. The critical part is to use "localhost" - this will work only when
  1265. accessed through update proxy and this is exactly what we want to
  1266. test here.
  1267. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1268. """
  1269. if self.template.count("debian") or self.template.count("whonix"):
  1270. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  1271. "rm -f /etc/apt/sources.list.d/* &&"
  1272. "echo 'deb [trusted=yes] http://localhost:8080 test main' "
  1273. "> /etc/apt/sources.list",
  1274. user="root"))
  1275. elif self.template.count("fedora"):
  1276. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  1277. "rm -f /etc/yum.repos.d/*.repo &&"
  1278. "echo '[test]' > /etc/yum.repos.d/test.repo &&"
  1279. "echo 'name=Test repo' >> /etc/yum.repos.d/test.repo &&"
  1280. "echo 'gpgcheck=0' >> /etc/yum.repos.d/test.repo &&"
  1281. "echo 'baseurl=http://localhost:8080/'"
  1282. " >> /etc/yum.repos.d/test.repo",
  1283. user="root"
  1284. ))
  1285. else:
  1286. # not reachable...
  1287. self.skipTest("Template {} not supported by this test".format(
  1288. self.template))
  1289. def test_010_update_via_proxy(self):
  1290. '''
  1291. Test both whether updates proxy works and whether is actually used
  1292. by the VM
  1293. :type self: qubes.tests.SystemTestCase | VmUpdatesMixin
  1294. '''
  1295. if self.template.count("minimal"):
  1296. self.skipTest("Template {} not supported by this test".format(
  1297. self.template))
  1298. self.netvm_repo = self.app.add_new_vm(
  1299. qubes.vm.appvm.AppVM,
  1300. name=self.make_vm_name('net'),
  1301. label='red')
  1302. self.netvm_repo.provides_network = True
  1303. self.loop.run_until_complete(self.netvm_repo.create_on_disk())
  1304. self.testvm1.netvm = self.netvm_repo
  1305. self.netvm_repo.features['service.qubes-updates-proxy'] = True
  1306. # TODO: consider also adding a test for the template itself
  1307. self.testvm1.features['service.updates-proxy-setup'] = True
  1308. self.app.save()
  1309. # Setup test repo
  1310. self.loop.run_until_complete(self.netvm_repo.start())
  1311. self.create_repo_and_serve()
  1312. # Configure local repo
  1313. self.loop.run_until_complete(self.testvm1.start())
  1314. self.configure_test_repo()
  1315. with self.qrexec_policy('qubes.UpdatesProxy', self.testvm1,
  1316. '$default', action='allow,target=' + self.netvm_repo.name):
  1317. # update repository metadata
  1318. self.assertRunCommandReturnCode(self.testvm1,
  1319. self.update_cmd, self.exit_code_ok)
  1320. # install test package
  1321. self.assertRunCommandReturnCode(self.testvm1,
  1322. self.install_cmd.format('test-pkg'), self.exit_code_ok)
  1323. # verify if it was really installed
  1324. self.assertRunCommandReturnCode(self.testvm1,
  1325. self.install_test_cmd.format('test-pkg'), self.exit_code_ok)
  1326. def test_020_updates_available_notification(self):
  1327. # override with StandaloneVM
  1328. self.testvm1 = self.app.add_new_vm(
  1329. qubes.vm.standalonevm.StandaloneVM,
  1330. name=self.make_vm_name('vm2'),
  1331. label='red')
  1332. tpl = self.app.domains[self.template]
  1333. self.testvm1.clone_properties(tpl)
  1334. self.testvm1.features.update(tpl.features)
  1335. self.loop.run_until_complete(
  1336. self.testvm1.clone_disk_files(tpl))
  1337. self.loop.run_until_complete(self.testvm1.start())
  1338. self.netvm_repo = self.testvm1
  1339. self.create_repo_and_serve()
  1340. self.configure_test_repo()
  1341. self.loop.run_until_complete(
  1342. self.testvm1.run_for_stdio(
  1343. '/usr/lib/qubes/upgrades-status-notify',
  1344. user='root',
  1345. ))
  1346. self.assertFalse(self.testvm1.features.get('updates-available', False))
  1347. # update repository metadata
  1348. self.assertRunCommandReturnCode(
  1349. self.testvm1, self.update_cmd, self.exit_code_ok)
  1350. # install test package
  1351. self.assertRunCommandReturnCode(
  1352. self.testvm1, self.install_cmd.format('test-pkg'), self.exit_code_ok)
  1353. self.assertFalse(self.testvm1.features.get('updates-available', False))
  1354. self.add_update_to_repo()
  1355. # update repository metadata
  1356. self.assertRunCommandReturnCode(
  1357. self.testvm1, self.update_cmd, self.exit_code_ok)
  1358. self.loop.run_until_complete(
  1359. self.testvm1.run_for_stdio(
  1360. '/usr/lib/qubes/upgrades-status-notify',
  1361. user='root',
  1362. ))
  1363. self.assertTrue(self.testvm1.features.get('updates-available', False))
  1364. # install updates
  1365. self.assertRunCommandReturnCode(
  1366. self.testvm1, self.upgrade_cmd, self.exit_code_ok)
  1367. self.assertFalse(self.testvm1.features.get('updates-available', False))
  1368. def create_testcases_for_templates():
  1369. yield from qubes.tests.create_testcases_for_templates('VmNetworking',
  1370. VmNetworkingMixin, qubes.tests.SystemTestCase,
  1371. module=sys.modules[__name__])
  1372. yield from qubes.tests.create_testcases_for_templates('VmIPv6Networking',
  1373. VmIPv6NetworkingMixin, qubes.tests.SystemTestCase,
  1374. module=sys.modules[__name__])
  1375. yield from qubes.tests.create_testcases_for_templates('VmUpdates',
  1376. VmUpdatesMixin, qubes.tests.SystemTestCase,
  1377. module=sys.modules[__name__])
  1378. def load_tests(loader, tests, pattern):
  1379. tests.addTests(loader.loadTestsFromNames(
  1380. create_testcases_for_templates()))
  1381. return tests
  1382. qubes.tests.maybe_create_testcases_on_import(create_testcases_for_templates)