network.py 66 KB

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