network.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335
  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 multiprocessing
  24. import os
  25. import subprocess
  26. import sys
  27. import time
  28. import unittest
  29. import qubes.tests
  30. import qubes.firewall
  31. import qubes.vm.appvm
  32. class NcVersion:
  33. Trad = 1
  34. Nmap = 2
  35. # noinspection PyAttributeOutsideInit
  36. class VmNetworkingMixin(object):
  37. test_ip = '192.168.123.45'
  38. test_name = 'test.example.com'
  39. ping_cmd = 'ping -W 1 -n -c 1 {target}'
  40. ping_ip = ping_cmd.format(target=test_ip)
  41. ping_name = ping_cmd.format(target=test_name)
  42. # filled by load_tests
  43. template = None
  44. def run_cmd(self, vm, cmd, user="root"):
  45. try:
  46. self.loop.run_until_complete(vm.run_for_stdio(cmd, user=user))
  47. except subprocess.CalledProcessError as e:
  48. return e.returncode
  49. return 0
  50. def check_nc_version(self, vm):
  51. if self.run_cmd(vm, 'nc -h >/dev/null 2>&1') != 0:
  52. self.skipTest('nc not installed')
  53. if self.run_cmd(vm, 'nc -h 2>&1|grep -q nmap.org') == 0:
  54. return NcVersion.Nmap
  55. else:
  56. return NcVersion.Trad
  57. def setUp(self):
  58. super(VmNetworkingMixin, self).setUp()
  59. if self.template.startswith('whonix-'):
  60. self.skipTest("Test not supported here - Whonix uses its own "
  61. "firewall settings")
  62. self.init_default_template(self.template)
  63. self.testnetvm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  64. name=self.make_vm_name('netvm1'),
  65. label='red')
  66. self.loop.run_until_complete(self.testnetvm.create_on_disk())
  67. self.testnetvm.provides_network = True
  68. self.testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  69. name=self.make_vm_name('vm1'),
  70. label='red')
  71. self.loop.run_until_complete(self.testvm1.create_on_disk())
  72. self.testvm1.netvm = self.testnetvm
  73. self.app.save()
  74. self.configure_netvm()
  75. def configure_netvm(self):
  76. def run_netvm_cmd(cmd):
  77. if self.run_cmd(self.testnetvm, cmd) != 0:
  78. self.fail("Command '%s' failed" % cmd)
  79. if not self.testnetvm.is_running():
  80. self.loop.run_until_complete(self.testnetvm.start())
  81. # Ensure that dnsmasq is installed:
  82. try:
  83. self.loop.run_until_complete(self.testnetvm.run_for_stdio(
  84. 'dnsmasq --version', user='root'))
  85. except subprocess.CalledProcessError:
  86. self.skipTest("dnsmasq not installed")
  87. run_netvm_cmd("ip link add test0 type dummy")
  88. run_netvm_cmd("ip link set test0 up")
  89. run_netvm_cmd("ip addr add {}/24 dev test0".format(self.test_ip))
  90. run_netvm_cmd("iptables -I INPUT -d {} -j ACCEPT".format(self.test_ip))
  91. # ignore failure
  92. self.run_cmd(self.testnetvm, "killall --wait dnsmasq")
  93. run_netvm_cmd("dnsmasq -a {ip} -A /{name}/{ip} -i test0 -z".format(
  94. ip=self.test_ip, name=self.test_name))
  95. run_netvm_cmd("echo nameserver {} > /etc/resolv.conf".format(
  96. self.test_ip))
  97. run_netvm_cmd("/usr/lib/qubes/qubes-setup-dnat-to-ns")
  98. def test_000_simple_networking(self):
  99. self.loop.run_until_complete(self.testvm1.start())
  100. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  101. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  102. def test_010_simple_proxyvm(self):
  103. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  104. name=self.make_vm_name('proxy'),
  105. label='red')
  106. self.proxy.provides_network = True
  107. self.proxy.netvm = self.testnetvm
  108. self.loop.run_until_complete(self.proxy.create_on_disk())
  109. self.testvm1.netvm = self.proxy
  110. self.app.save()
  111. self.loop.run_until_complete(self.testvm1.start())
  112. self.assertTrue(self.proxy.is_running())
  113. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  114. "Ping by IP from ProxyVM failed")
  115. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  116. "Ping by name from ProxyVM failed")
  117. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  118. "Ping by IP from AppVM failed")
  119. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  120. "Ping by IP from AppVM failed")
  121. @qubes.tests.expectedFailureIfTemplate('debian-7')
  122. @unittest.skipUnless(spawn.find_executable('xdotool'),
  123. "xdotool not installed")
  124. def test_020_simple_proxyvm_nm(self):
  125. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  126. name=self.make_vm_name('proxy'),
  127. label='red')
  128. self.proxy.provides_network = True
  129. self.loop.run_until_complete(self.proxy.create_on_disk())
  130. self.proxy.netvm = self.testnetvm
  131. self.proxy.features['service.network-manager'] = True
  132. self.testvm1.netvm = self.proxy
  133. self.app.save()
  134. self.loop.run_until_complete(self.testvm1.start())
  135. self.assertTrue(self.proxy.is_running())
  136. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  137. "Ping by IP failed")
  138. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  139. "Ping by name failed")
  140. # reconnect to make sure that device was configured by NM
  141. self.assertEqual(
  142. self.run_cmd(self.proxy, "nmcli device disconnect eth0",
  143. user="user"),
  144. 0, "Failed to disconnect eth0 using nmcli")
  145. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  146. "Network should be disabled, but apparently it isn't")
  147. self.assertEqual(
  148. self.run_cmd(self.proxy,
  149. 'nmcli connection up "VM uplink eth0" ifname eth0',
  150. user="user"),
  151. 0, "Failed to connect eth0 using nmcli")
  152. self.assertEqual(self.run_cmd(self.proxy, "nm-online", user="user"), 0,
  153. "Failed to wait for NM connection")
  154. # check for nm-applet presence
  155. self.assertEqual(subprocess.call([
  156. 'xdotool', 'search', '--class', '{}:nm-applet'.format(
  157. self.proxy.name)],
  158. stdout=subprocess.DEVNULL), 0, "nm-applet window not found")
  159. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  160. "Ping by IP failed (after NM reconnection")
  161. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  162. "Ping by name failed (after NM reconnection)")
  163. def test_030_firewallvm_firewall(self):
  164. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  165. name=self.make_vm_name('proxy'),
  166. label='red')
  167. self.proxy.provides_network = True
  168. self.loop.run_until_complete(self.proxy.create_on_disk())
  169. self.proxy.netvm = self.testnetvm
  170. self.testvm1.netvm = self.proxy
  171. self.app.save()
  172. nc_version = self.check_nc_version(self.testnetvm)
  173. # block all for first
  174. self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')]
  175. self.testvm1.firewall.save()
  176. self.loop.run_until_complete(self.testvm1.start())
  177. self.assertTrue(self.proxy.is_running())
  178. nc = self.loop.run_until_complete(self.testnetvm.run(
  179. 'nc -l --send-only -e /bin/hostname -k 1234'
  180. if nc_version == NcVersion.Nmap
  181. else 'while nc -l -e /bin/hostname -p 1234; do true; done'))
  182. try:
  183. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  184. "Ping by IP from ProxyVM failed")
  185. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  186. "Ping by name from ProxyVM failed")
  187. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  188. "Ping by IP should be blocked")
  189. if nc_version == NcVersion.Nmap:
  190. nc_cmd = "nc -w 1 --recv-only {} 1234".format(self.test_ip)
  191. else:
  192. nc_cmd = "nc -w 1 {} 1234".format(self.test_ip)
  193. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  194. "TCP connection should be blocked")
  195. # block all except ICMP
  196. self.testvm1.firewall.rules = [(
  197. qubes.firewall.Rule(None, action='accept', proto='icmp')
  198. )]
  199. self.testvm1.firewall.save()
  200. # Ugly hack b/c there is no feedback when the rules are actually
  201. # applied
  202. time.sleep(3)
  203. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  204. "Ping by IP failed (should be allowed now)")
  205. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  206. "Ping by name should be blocked")
  207. # all TCP still blocked
  208. self.testvm1.firewall.rules = [
  209. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  210. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  211. ]
  212. self.testvm1.firewall.save()
  213. # Ugly hack b/c there is no feedback when the rules are actually
  214. # applied
  215. time.sleep(3)
  216. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  217. "Ping by name failed (should be allowed now)")
  218. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  219. "TCP connection should be blocked")
  220. # block all except target
  221. self.testvm1.firewall.rules = [
  222. qubes.firewall.Rule(None, action='accept', dsthost=self.test_ip,
  223. proto='tcp', dstports=1234),
  224. ]
  225. self.testvm1.firewall.save()
  226. # Ugly hack b/c there is no feedback when the rules are actually
  227. # applied
  228. time.sleep(3)
  229. self.assertEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  230. "TCP connection failed (should be allowed now)")
  231. # allow all except target
  232. self.testvm1.firewall.rules = [
  233. qubes.firewall.Rule(None, action='drop', dsthost=self.test_ip,
  234. proto='tcp', dstports=1234),
  235. qubes.firewall.Rule(action='accept'),
  236. ]
  237. self.testvm1.firewall.save()
  238. # Ugly hack b/c there is no feedback when the rules are actually
  239. # applied
  240. time.sleep(3)
  241. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  242. "TCP connection should be blocked")
  243. finally:
  244. nc.terminate()
  245. self.loop.run_until_complete(nc.wait())
  246. def test_040_inter_vm(self):
  247. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  248. name=self.make_vm_name('proxy'),
  249. label='red')
  250. self.loop.run_until_complete(self.proxy.create_on_disk())
  251. self.proxy.provides_network = True
  252. self.proxy.netvm = self.testnetvm
  253. self.testvm1.netvm = self.proxy
  254. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  255. name=self.make_vm_name('vm2'),
  256. label='red')
  257. self.loop.run_until_complete(self.testvm2.create_on_disk())
  258. self.testvm2.netvm = self.proxy
  259. self.app.save()
  260. self.loop.run_until_complete(asyncio.wait([
  261. self.testvm1.start(),
  262. self.testvm2.start()]))
  263. self.assertNotEqual(self.run_cmd(self.testvm1,
  264. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  265. self.testvm2.netvm = self.testnetvm
  266. self.assertNotEqual(self.run_cmd(self.testvm1,
  267. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  268. self.assertNotEqual(self.run_cmd(self.testvm2,
  269. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  270. self.testvm1.netvm = self.testnetvm
  271. self.assertNotEqual(self.run_cmd(self.testvm1,
  272. self.ping_cmd.format(target=self.testvm2.ip)), 0)
  273. self.assertNotEqual(self.run_cmd(self.testvm2,
  274. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  275. def test_050_spoof_ip(self):
  276. """Test if VM IP spoofing is blocked"""
  277. self.loop.run_until_complete(self.testvm1.start())
  278. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  279. self.assertEqual(self.run_cmd(self.testnetvm,
  280. 'iptables -I INPUT -i vif+ ! -s {} -p icmp -j LOG'.format(
  281. self.testvm1.ip)), 0)
  282. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  283. 'ip addr flush dev eth0 && '
  284. 'ip addr add 10.137.1.128/24 dev eth0 && '
  285. 'ip route add default dev eth0',
  286. user='root'))
  287. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  288. "Spoofed ping should be blocked")
  289. try:
  290. (output, _) = self.loop.run_until_complete(
  291. self.testnetvm.run_for_stdio('iptables -nxvL INPUT',
  292. user='root'))
  293. except subprocess.CalledProcessError:
  294. self.fail('iptables -nxvL INPUT failed')
  295. output = output.decode().splitlines()
  296. packets = output[2].lstrip().split()[0]
  297. self.assertEquals(packets, '0', 'Some packet hit the INPUT rule')
  298. def test_100_late_xldevd_startup(self):
  299. """Regression test for #1990"""
  300. # Simulater late xl devd startup
  301. cmd = "systemctl stop xendriverdomain"
  302. if self.run_cmd(self.testnetvm, cmd) != 0:
  303. self.fail("Command '%s' failed" % cmd)
  304. self.loop.run_until_complete(self.testvm1.start())
  305. cmd = "systemctl start xendriverdomain"
  306. if self.run_cmd(self.testnetvm, cmd) != 0:
  307. self.fail("Command '%s' failed" % cmd)
  308. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  309. def test_200_fake_ip_simple(self):
  310. '''Test hiding VM real IP'''
  311. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  312. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  313. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  314. self.app.save()
  315. self.loop.run_until_complete(self.testvm1.start())
  316. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  317. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  318. try:
  319. (output, _) = self.loop.run_until_complete(
  320. self.testvm1.run_for_stdio(
  321. 'ip addr show dev eth0', user='root'))
  322. except subprocess.CalledProcessError:
  323. self.fail('ip addr show dev eth0 failed')
  324. output = output.decode()
  325. self.assertIn('192.168.1.128', output)
  326. self.assertNotIn(str(self.testvm1.ip), output)
  327. try:
  328. (output, _) = self.loop.run_until_complete(
  329. self.testvm1.run_for_stdio('ip route show', user='root'))
  330. except subprocess.CalledProcessError:
  331. self.fail('ip route show failed')
  332. output = output.decode()
  333. self.assertIn('192.168.1.1', output)
  334. self.assertNotIn(str(self.testvm1.netvm.ip), output)
  335. def test_201_fake_ip_without_gw(self):
  336. '''Test hiding VM real IP'''
  337. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  338. self.app.save()
  339. self.loop.run_until_complete(self.testvm1.start())
  340. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  341. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  342. try:
  343. (output, _) = self.loop.run_until_complete(
  344. self.testvm1.run_for_stdio('ip addr show dev eth0',
  345. user='root'))
  346. except subprocess.CalledProcessError:
  347. self.fail('ip addr show dev eth0 failed')
  348. output = output.decode()
  349. self.assertIn('192.168.1.128', output)
  350. self.assertNotIn(str(self.testvm1.ip), output)
  351. def test_202_fake_ip_firewall(self):
  352. '''Test hiding VM real IP, firewall'''
  353. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  354. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  355. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  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.loop.run_until_complete(self.proxy.create_on_disk())
  361. self.proxy.netvm = self.testnetvm
  362. self.testvm1.netvm = self.proxy
  363. self.app.save()
  364. nc_version = self.check_nc_version(self.testnetvm)
  365. # block all but ICMP and DNS
  366. self.testvm1.firewall.rules = [
  367. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  368. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  369. ]
  370. self.testvm1.firewall.save()
  371. self.loop.run_until_complete(self.testvm1.start())
  372. self.assertTrue(self.proxy.is_running())
  373. nc = self.loop.run_until_complete(self.testnetvm.run(
  374. 'nc -l --send-only -e /bin/hostname -k 1234'
  375. if nc_version == NcVersion.Nmap
  376. else 'while nc -l -e /bin/hostname -p 1234; do true; done'))
  377. try:
  378. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  379. "Ping by IP from ProxyVM failed")
  380. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  381. "Ping by name from ProxyVM failed")
  382. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  383. "Ping by IP should be allowed")
  384. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  385. "Ping by name should be allowed")
  386. if nc_version == NcVersion.Nmap:
  387. nc_cmd = "nc -w 1 --recv-only {} 1234".format(self.test_ip)
  388. else:
  389. nc_cmd = "nc -w 1 {} 1234".format(self.test_ip)
  390. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  391. "TCP connection should be blocked")
  392. finally:
  393. nc.terminate()
  394. self.loop.run_until_complete(nc.wait())
  395. def test_203_fake_ip_inter_vm_allow(self):
  396. '''Access VM with "fake IP" from other VM (when firewall allows)'''
  397. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  398. name=self.make_vm_name('proxy'),
  399. label='red')
  400. self.loop.run_until_complete(self.proxy.create_on_disk())
  401. self.proxy.provides_network = True
  402. self.proxy.netvm = self.testnetvm
  403. self.testvm1.netvm = self.proxy
  404. self.testvm1.features['net.fake-ip'] = '192.168.1.128'
  405. self.testvm1.features['net.fake-gateway'] = '192.168.1.1'
  406. self.testvm1.features['net.fake-netmask'] = '255.255.255.0'
  407. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  408. name=self.make_vm_name('vm2'),
  409. label='red')
  410. self.loop.run_until_complete(self.testvm2.create_on_disk())
  411. self.testvm2.netvm = self.proxy
  412. self.app.save()
  413. self.loop.run_until_complete(self.testvm1.start())
  414. self.loop.run_until_complete(self.testvm2.start())
  415. try:
  416. cmd = 'iptables -I FORWARD -s {} -d {} -j ACCEPT'.format(
  417. self.testvm2.ip, self.testvm1.ip)
  418. self.loop.run_until_complete(self.proxy.run_for_stdio(
  419. cmd, user='root'))
  420. except subprocess.CalledProcessError as e:
  421. raise AssertionError(
  422. '{} failed with: {}'.format(cmd, e.returncode)) from None
  423. try:
  424. cmd = 'iptables -I INPUT -s {} -j ACCEPT'.format(self.testvm2.ip)
  425. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  426. cmd, user='root'))
  427. except subprocess.CalledProcessError as e:
  428. raise AssertionError(
  429. '{} failed with: {}'.format(cmd, e.returncode)) from None
  430. self.assertEqual(self.run_cmd(self.testvm2,
  431. self.ping_cmd.format(target=self.testvm1.ip)), 0)
  432. try:
  433. cmd = 'iptables -nvxL INPUT | grep {}'.format(self.testvm2.ip)
  434. (stdout, _) = self.loop.run_until_complete(
  435. self.testvm1.run_for_stdio(cmd, user='root'))
  436. except subprocess.CalledProcessError as e:
  437. raise AssertionError(
  438. '{} failed with {}'.format(cmd, e.returncode)) from None
  439. self.assertNotEqual(stdout.decode().split()[0], '0',
  440. 'Packets didn\'t managed to the VM')
  441. def test_204_fake_ip_proxy(self):
  442. '''Test hiding VM real IP'''
  443. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  444. name=self.make_vm_name('proxy'),
  445. label='red')
  446. self.loop.run_until_complete(self.proxy.create_on_disk())
  447. self.proxy.provides_network = True
  448. self.proxy.netvm = self.testnetvm
  449. self.proxy.features['net.fake-ip'] = '192.168.1.128'
  450. self.proxy.features['net.fake-gateway'] = '192.168.1.1'
  451. self.proxy.features['net.fake-netmask'] = '255.255.255.0'
  452. self.testvm1.netvm = self.proxy
  453. self.app.save()
  454. self.loop.run_until_complete(self.testvm1.start())
  455. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0)
  456. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0)
  457. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  458. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  459. try:
  460. (output, _) = self.loop.run_until_complete(
  461. self.proxy.run_for_stdio(
  462. 'ip addr show dev eth0', user='root'))
  463. except subprocess.CalledProcessError as e:
  464. self.fail('ip addr show dev eth0 failed')
  465. output = output.decode()
  466. self.assertIn('192.168.1.128', output)
  467. self.assertNotIn(str(self.testvm1.ip), output)
  468. try:
  469. (output, _) = self.loop.run_until_complete(
  470. self.proxy.run_for_stdio(
  471. 'ip route show', user='root'))
  472. except subprocess.CalledProcessError as e:
  473. self.fail('ip route show failed')
  474. output = output.decode()
  475. self.assertIn('192.168.1.1', output)
  476. self.assertNotIn(str(self.testvm1.netvm.ip), output)
  477. try:
  478. (output, _) = self.loop.run_until_complete(
  479. self.testvm1.run_for_stdio(
  480. 'ip addr show dev eth0', user='root'))
  481. except subprocess.CalledProcessError as e:
  482. self.fail('ip addr show dev eth0 failed')
  483. output = output.decode()
  484. self.assertNotIn('192.168.1.128', output)
  485. self.assertIn(str(self.testvm1.ip), output)
  486. try:
  487. (output, _) = self.loop.run_until_complete(
  488. self.testvm1.run_for_stdio(
  489. 'ip route show', user='root'))
  490. except subprocess.CalledProcessError as e:
  491. self.fail('ip route show failed')
  492. output = output.decode()
  493. self.assertIn('192.168.1.128', output)
  494. self.assertNotIn(str(self.proxy.ip), output)
  495. def test_210_custom_ip_simple(self):
  496. '''Custom AppVM IP'''
  497. self.testvm1.ip = '192.168.1.1'
  498. self.app.save()
  499. self.loop.run_until_complete(self.testvm1.start())
  500. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  501. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  502. def test_211_custom_ip_proxy(self):
  503. '''Custom ProxyVM IP'''
  504. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  505. name=self.make_vm_name('proxy'),
  506. label='red')
  507. self.loop.run_until_complete(self.proxy.create_on_disk())
  508. self.proxy.provides_network = True
  509. self.proxy.netvm = self.testnetvm
  510. self.proxy.ip = '192.168.1.1'
  511. self.testvm1.netvm = self.proxy
  512. self.app.save()
  513. self.loop.run_until_complete(self.testvm1.start())
  514. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
  515. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
  516. def test_212_custom_ip_firewall(self):
  517. '''Custom VM IP and firewall'''
  518. self.testvm1.ip = '192.168.1.1'
  519. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  520. name=self.make_vm_name('proxy'),
  521. label='red')
  522. self.proxy.provides_network = True
  523. self.loop.run_until_complete(self.proxy.create_on_disk())
  524. self.proxy.netvm = self.testnetvm
  525. self.testvm1.netvm = self.proxy
  526. self.app.save()
  527. nc_version = self.check_nc_version(self.testnetvm)
  528. # block all but ICMP and DNS
  529. self.testvm1.firewall.rules = [
  530. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  531. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  532. ]
  533. self.testvm1.firewall.save()
  534. self.loop.run_until_complete(self.testvm1.start())
  535. self.assertTrue(self.proxy.is_running())
  536. nc = self.loop.run_until_complete(self.testnetvm.run(
  537. 'nc -l --send-only -e /bin/hostname -k 1234'
  538. if nc_version == NcVersion.Nmap
  539. else 'while nc -l -e /bin/hostname -p 1234; do true; done'))
  540. try:
  541. self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0,
  542. "Ping by IP from ProxyVM failed")
  543. self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0,
  544. "Ping by name from ProxyVM failed")
  545. self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
  546. "Ping by IP should be allowed")
  547. self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0,
  548. "Ping by name should be allowed")
  549. if nc_version == NcVersion.Nmap:
  550. nc_cmd = "nc -w 1 --recv-only {} 1234".format(self.test_ip)
  551. else:
  552. nc_cmd = "nc -w 1 {} 1234".format(self.test_ip)
  553. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  554. "TCP connection should be blocked")
  555. finally:
  556. nc.terminate()
  557. self.loop.run_until_complete(nc.wait())
  558. class VmIPv6NetworkingMixin(VmNetworkingMixin):
  559. test_ip6 = '2000:abcd::1'
  560. ping6_cmd = 'ping6 -W 1 -n -c 1 {target}'
  561. def setUp(self):
  562. super(VmIPv6NetworkingMixin, self).setUp()
  563. self.ping6_ip = self.ping6_cmd.format(target=self.test_ip6)
  564. self.ping6_name = self.ping6_cmd.format(target=self.test_name)
  565. def configure_netvm(self):
  566. self.testnetvm.features['ipv6'] = True
  567. super(VmIPv6NetworkingMixin, self).configure_netvm()
  568. def run_netvm_cmd(cmd):
  569. if self.run_cmd(self.testnetvm, cmd) != 0:
  570. self.fail("Command '%s' failed" % cmd)
  571. run_netvm_cmd("ip addr add {}/128 dev test0".format(self.test_ip6))
  572. run_netvm_cmd(
  573. "ip6tables -I INPUT -d {} -j ACCEPT".format(self.test_ip6))
  574. # ignore failure
  575. self.run_cmd(self.testnetvm, "killall --wait dnsmasq")
  576. run_netvm_cmd(
  577. "dnsmasq -a {ip} -A /{name}/{ip} -A /{name}/{ip6} -i test0 -z".
  578. format(ip=self.test_ip, ip6=self.test_ip6, name=self.test_name))
  579. def test_500_ipv6_simple_networking(self):
  580. self.loop.run_until_complete(self.testvm1.start())
  581. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  582. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  583. def test_510_ipv6_simple_proxyvm(self):
  584. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  585. name=self.make_vm_name('proxy'),
  586. label='red')
  587. self.proxy.provides_network = True
  588. self.proxy.netvm = self.testnetvm
  589. self.loop.run_until_complete(self.proxy.create_on_disk())
  590. self.testvm1.netvm = self.proxy
  591. self.app.save()
  592. self.loop.run_until_complete(self.testvm1.start())
  593. self.assertTrue(self.proxy.is_running())
  594. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  595. "Ping by IP from ProxyVM failed")
  596. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  597. "Ping by name from ProxyVM failed")
  598. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  599. "Ping by IP from AppVM failed")
  600. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  601. "Ping by IP from AppVM failed")
  602. @qubes.tests.expectedFailureIfTemplate('debian-7')
  603. @unittest.skipUnless(spawn.find_executable('xdotool'),
  604. "xdotool not installed")
  605. def test_520_ipv6_simple_proxyvm_nm(self):
  606. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  607. name=self.make_vm_name('proxy'),
  608. label='red')
  609. self.proxy.provides_network = True
  610. self.loop.run_until_complete(self.proxy.create_on_disk())
  611. self.proxy.netvm = self.testnetvm
  612. self.proxy.features['service.network-manager'] = True
  613. self.testvm1.netvm = self.proxy
  614. self.app.save()
  615. self.loop.run_until_complete(self.testvm1.start())
  616. self.assertTrue(self.proxy.is_running())
  617. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  618. "Ping by IP failed")
  619. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  620. "Ping by name failed")
  621. # reconnect to make sure that device was configured by NM
  622. self.assertEqual(
  623. self.run_cmd(self.proxy, "nmcli device disconnect eth0",
  624. user="user"),
  625. 0, "Failed to disconnect eth0 using nmcli")
  626. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  627. "Network should be disabled, but apparently it isn't")
  628. self.assertEqual(
  629. self.run_cmd(self.proxy,
  630. 'nmcli connection up "VM uplink eth0" ifname eth0',
  631. user="user"),
  632. 0, "Failed to connect eth0 using nmcli")
  633. self.assertEqual(self.run_cmd(self.proxy, "nm-online",
  634. user="user"), 0,
  635. "Failed to wait for NM connection")
  636. # wait for duplicate-address-detection to complete - by default it has
  637. # 1s timeout
  638. time.sleep(2)
  639. # check for nm-applet presence
  640. self.assertEqual(subprocess.call([
  641. 'xdotool', 'search', '--class', '{}:nm-applet'.format(
  642. self.proxy.name)],
  643. stdout=subprocess.DEVNULL), 0, "nm-applet window not found")
  644. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  645. "Ping by IP failed (after NM reconnection")
  646. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  647. "Ping by name failed (after NM reconnection)")
  648. def test_530_ipv6_firewallvm_firewall(self):
  649. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  650. name=self.make_vm_name('proxy'),
  651. label='red')
  652. self.proxy.provides_network = True
  653. self.loop.run_until_complete(self.proxy.create_on_disk())
  654. self.proxy.netvm = self.testnetvm
  655. self.testvm1.netvm = self.proxy
  656. self.app.save()
  657. if self.run_cmd(self.testnetvm, 'ncat -h') != 0:
  658. self.skipTest('nmap ncat not installed')
  659. # block all for first
  660. self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')]
  661. self.testvm1.firewall.save()
  662. self.loop.run_until_complete(self.testvm1.start())
  663. self.assertTrue(self.proxy.is_running())
  664. nc = self.loop.run_until_complete(self.testnetvm.run(
  665. 'ncat -l --send-only -e /bin/hostname -k 1234'))
  666. try:
  667. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  668. "Ping by IP from ProxyVM failed")
  669. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  670. "Ping by name from ProxyVM failed")
  671. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  672. "Ping by IP should be blocked")
  673. nc_cmd = "ncat -w 1 --recv-only {} 1234".format(self.test_ip6)
  674. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  675. "TCP connection should be blocked")
  676. # block all except ICMP
  677. self.testvm1.firewall.rules = [(
  678. qubes.firewall.Rule(None, action='accept', proto='icmp')
  679. )]
  680. self.testvm1.firewall.save()
  681. # Ugly hack b/c there is no feedback when the rules are actually
  682. # applied
  683. time.sleep(3)
  684. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  685. "Ping by IP failed (should be allowed now)")
  686. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  687. "Ping by name should be blocked")
  688. # all TCP still blocked
  689. self.testvm1.firewall.rules = [
  690. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  691. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  692. ]
  693. self.testvm1.firewall.save()
  694. # Ugly hack b/c there is no feedback when the rules are actually
  695. # applied
  696. time.sleep(3)
  697. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  698. "Ping by name failed (should be allowed now)")
  699. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  700. "TCP connection should be blocked")
  701. # block all except target
  702. self.testvm1.firewall.rules = [
  703. qubes.firewall.Rule(None, action='accept', dsthost=self.test_ip6,
  704. proto='tcp', dstports=1234),
  705. ]
  706. self.testvm1.firewall.save()
  707. # Ugly hack b/c there is no feedback when the rules are actually
  708. # applied
  709. time.sleep(3)
  710. self.assertEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  711. "TCP connection failed (should be allowed now)")
  712. # block all except target - by name
  713. self.testvm1.firewall.rules = [
  714. qubes.firewall.Rule(None, action='accept',
  715. dsthost=self.test_name,
  716. proto='tcp', dstports=1234),
  717. ]
  718. self.testvm1.firewall.save()
  719. # Ugly hack b/c there is no feedback when the rules are actually
  720. # applied
  721. time.sleep(3)
  722. self.assertEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  723. "TCP (IPv6) connection failed (should be allowed now)")
  724. self.assertEqual(self.run_cmd(self.testvm1,
  725. nc_cmd.replace(self.test_ip6, self.test_ip)),
  726. 0,
  727. "TCP (IPv4) connection failed (should be allowed now)")
  728. # allow all except target
  729. self.testvm1.firewall.rules = [
  730. qubes.firewall.Rule(None, action='drop', dsthost=self.test_ip6,
  731. proto='tcp', dstports=1234),
  732. qubes.firewall.Rule(action='accept'),
  733. ]
  734. self.testvm1.firewall.save()
  735. # Ugly hack b/c there is no feedback when the rules are actually
  736. # applied
  737. time.sleep(3)
  738. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  739. "TCP connection should be blocked")
  740. finally:
  741. nc.terminate()
  742. self.loop.run_until_complete(nc.wait())
  743. def test_540_ipv6_inter_vm(self):
  744. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  745. name=self.make_vm_name('proxy'),
  746. label='red')
  747. self.loop.run_until_complete(self.proxy.create_on_disk())
  748. self.proxy.provides_network = True
  749. self.proxy.netvm = self.testnetvm
  750. self.testvm1.netvm = self.proxy
  751. self.testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  752. name=self.make_vm_name('vm2'),
  753. label='red')
  754. self.loop.run_until_complete(self.testvm2.create_on_disk())
  755. self.testvm2.netvm = self.proxy
  756. self.app.save()
  757. self.loop.run_until_complete(asyncio.wait([
  758. self.testvm1.start(),
  759. self.testvm2.start()]))
  760. self.assertNotEqual(self.run_cmd(self.testvm1,
  761. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  762. self.testvm2.netvm = self.testnetvm
  763. self.assertNotEqual(self.run_cmd(self.testvm1,
  764. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  765. self.assertNotEqual(self.run_cmd(self.testvm2,
  766. self.ping_cmd.format(target=self.testvm1.ip6)), 0)
  767. self.testvm1.netvm = self.testnetvm
  768. self.assertNotEqual(self.run_cmd(self.testvm1,
  769. self.ping_cmd.format(target=self.testvm2.ip6)), 0)
  770. self.assertNotEqual(self.run_cmd(self.testvm2,
  771. self.ping_cmd.format(target=self.testvm1.ip6)), 0)
  772. def test_550_ipv6_spoof_ip(self):
  773. """Test if VM IP spoofing is blocked"""
  774. self.loop.run_until_complete(self.testvm1.start())
  775. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  776. # add a simple rule counting packets
  777. self.assertEqual(self.run_cmd(self.testnetvm,
  778. 'ip6tables -I INPUT -i vif+ ! -s {} -p icmpv6 -j LOG'.format(
  779. self.testvm1.ip6)), 0)
  780. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  781. 'ip -6 addr flush dev eth0 && '
  782. 'ip -6 addr add {}/128 dev eth0 && '
  783. 'ip -6 route add default via {} dev eth0'.format(
  784. str(self.testvm1.visible_ip6) + '1',
  785. str(self.testvm1.visible_gateway6)),
  786. user='root'))
  787. self.assertNotEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  788. "Spoofed ping should be blocked")
  789. try:
  790. (output, _) = self.loop.run_until_complete(
  791. self.testnetvm.run_for_stdio('ip6tables -nxvL INPUT',
  792. user='root'))
  793. except subprocess.CalledProcessError:
  794. self.fail('ip6tables -nxvL INPUT failed')
  795. output = output.decode().splitlines()
  796. packets = output[2].lstrip().split()[0]
  797. self.assertEquals(packets, '0', 'Some packet hit the INPUT rule')
  798. def test_710_ipv6_custom_ip_simple(self):
  799. '''Custom AppVM IP'''
  800. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  801. self.app.save()
  802. self.loop.run_until_complete(self.testvm1.start())
  803. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  804. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  805. def test_711_ipv6_custom_ip_proxy(self):
  806. '''Custom ProxyVM IP'''
  807. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  808. name=self.make_vm_name('proxy'),
  809. label='red')
  810. self.loop.run_until_complete(self.proxy.create_on_disk())
  811. self.proxy.provides_network = True
  812. self.proxy.netvm = self.testnetvm
  813. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  814. self.testvm1.netvm = self.proxy
  815. self.app.save()
  816. self.loop.run_until_complete(self.testvm1.start())
  817. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0)
  818. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0)
  819. def test_712_ipv6_custom_ip_firewall(self):
  820. '''Custom VM IP and firewall'''
  821. self.testvm1.ip6 = '2000:aaaa:bbbb::1'
  822. self.proxy = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  823. name=self.make_vm_name('proxy'),
  824. label='red')
  825. self.proxy.provides_network = True
  826. self.loop.run_until_complete(self.proxy.create_on_disk())
  827. self.proxy.netvm = self.testnetvm
  828. self.testvm1.netvm = self.proxy
  829. self.app.save()
  830. nc_version = self.check_nc_version(self.testnetvm)
  831. # block all but ICMP and DNS
  832. self.testvm1.firewall.rules = [
  833. qubes.firewall.Rule(None, action='accept', proto='icmp'),
  834. qubes.firewall.Rule(None, action='accept', specialtarget='dns'),
  835. ]
  836. self.testvm1.firewall.save()
  837. self.loop.run_until_complete(self.testvm1.start())
  838. self.assertTrue(self.proxy.is_running())
  839. nc = self.loop.run_until_complete(self.testnetvm.run(
  840. 'nc -l --send-only -e /bin/hostname -k 1234'
  841. if nc_version == NcVersion.Nmap
  842. else 'while nc -l -e /bin/hostname -p 1234; do true; done'))
  843. try:
  844. self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0,
  845. "Ping by IP from ProxyVM failed")
  846. self.assertEqual(self.run_cmd(self.proxy, self.ping6_name), 0,
  847. "Ping by name from ProxyVM failed")
  848. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0,
  849. "Ping by IP should be allowed")
  850. self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0,
  851. "Ping by name should be allowed")
  852. if nc_version == NcVersion.Nmap:
  853. nc_cmd = "nc -w 1 --recv-only {} 1234".format(self.test_ip6)
  854. else:
  855. nc_cmd = "nc -w 1 {} 1234".format(self.test_ip6)
  856. self.assertNotEqual(self.run_cmd(self.testvm1, nc_cmd), 0,
  857. "TCP connection should be blocked")
  858. finally:
  859. nc.terminate()
  860. self.loop.run_until_complete(nc.wait())
  861. # noinspection PyAttributeOutsideInit
  862. class VmUpdatesMixin(object):
  863. """
  864. Tests for VM updates
  865. """
  866. # filled by load_tests
  867. template = None
  868. # made this way to work also when no package build tools are installed
  869. """
  870. $ cat test-pkg.spec:
  871. Name: test-pkg
  872. Version: 1.0
  873. Release: 1%{?dist}
  874. Summary: Test package
  875. Group: System
  876. License: GPL
  877. URL: http://example.com/
  878. %description
  879. Test package
  880. %files
  881. %changelog
  882. $ rpmbuild -bb test-pkg.spec
  883. $ cat test-pkg-1.0-1.fc21.x86_64.rpm | gzip | base64
  884. """
  885. RPM_PACKAGE_GZIP_BASE64 = (
  886. b"H4sIAPzRLlYAA+2Y728URRjHn7ueUCkERKJVJDnTxLSxs7293o8WOER6ljYYrtKCLUSa3"
  887. b"bnZ64bd22VmTq8nr4wJbwxvjNHIG0x8oTHGGCHB8AcYE1/0lS80GgmQFCJU3wgB4ZjdfZ"
  888. b"q2xDe8NNlvMjfzmeeZH7tPbl98b35169cOUEpIJiTxT9SIrmVUs2hWh8dUAp54dOrM14s"
  889. b"JHK4D2DKl+j2qrVfjsuq3qEWbohjuAB2Lqk+p1o/8Z5QPmSi/YwnjezH+F8bLQZjqllW0"
  890. b"hvODRmFIL5hFk9JMXi/mi5ZuDleNwSEzP5wtmLnouNQnm3/6fndz7FLt9M/Hruj37gav4"
  891. b"tTjPnasWLFixYoVK1asWLFixYoV63+p0KNot9vnIPQc1vgYOwCSgXfxCoS+QzKHOVXVOj"
  892. b"Fn2ccIfI0k8nXkLuQbyJthxed4UrVnkG8i9yDfgsj3yCAv4foc8t+w1hf5B+Nl5Du43xj"
  893. b"yvxivIN9HpsgPkO2IU9uQfeRn8Xk/iJ4x1Y3nfxH1qecwfhH5+YgT25F7o/0SRdxvOppP"
  894. b"7MX9ZjB/DNnE/OOYX404uRGZIT+FbCFvQ3aQ8f0+/WF0XjJ8nyOw7H+BrmUA/a8pNZf2D"
  895. b"XrCqLG1cERbWHI8ajhznpBY9P0Tr8PkvJDMhTkp/Z0DA6xpuL7DNOq5A+DY9UYTmkOF2U"
  896. b"IO/sNt0wSnGvfdlZssD3rVIlLI9UUX37C6qXzHNntHPNfnTAhWHbUddtBwmegDjAUzZbu"
  897. b"m9lqZmzDmHc8Ik8WY8Tab4Myym4+Gx8V0qw8GtYyWIzrktEJwV9UHv3ktG471rAqHTmFQ"
  898. b"685V5uGqIalk06SWJr7tszR503Ac9cs493jJ8rhrSCIYbXBbzqt5v5+UZ0crh6bGR2dmJ"
  899. b"yuHD428VlLLLdakzJe2VxcKhFSFID73JKPS40RI7tXVCcQ3uOGWhPCJ2bAspiJ2i5Vy6n"
  900. b"jOqMerpEYpEe/Yks4xkU4Tt6BirmzUWanG6ozbFKhve9BsQRaLRTirzqk7hgUktXojKnf"
  901. b"n8jeg3X4QepP3i63po6oml+9t/CwJLya2Bn/ei6f7/4B3Ycdb0L3pt5Q5mNz16rWJ9fLk"
  902. b"vvOff/nxS7//8O2P2gvt7nDDnoV9L1du9N4+ucjl9u/8+a7dC5Nnvjlv9Ox5r+v9Cy0NE"
  903. b"m+c6rv60S/dZw98Gn6MNswcfQiWUvg3wBUAAA=="
  904. )
  905. """
  906. Minimal package generated by running dh_make on empty directory
  907. Then cat test-pkg_1.0-1_amd64.deb | gzip | base64
  908. """
  909. DEB_PACKAGE_GZIP_BASE64 = (
  910. b"H4sIACTXLlYAA1O0SSxKzrDjSklNykzM003KzEssqlRQUDA0MTG1NDQwNDVTUDBQAAEIa"
  911. b"WhgYGZioqBgogADCVxGegZcyfl5JUX5OXoliUV66VVE6DcwheuX7+ZgAAEW5rdXHb0PG4"
  912. b"iwf5j3WfMT6zWzzMuZgoE3jjYraNzbbFKWGms0SaRw/r2SV23WZ4IdP8preM4yqf0jt95"
  913. b"3c8qnacfNxJUkf9/w+/3X9ph2GEdgQdixrz/niHKKTnYXizf4oSC7tHOz2Zzq+/6vn8/7"
  914. b"ezQ7c1tmi7xZ3SGJ4yzhT2dcr7V+W3zM5ZPu/56PSv4Zdok+7Yv/V/6buWaKVlFkkV58S"
  915. b"N3GmLgnqzRmeZ3V3ymmurS5fGa85/LNx1bpZMin3S6dvXKqydp3ubP1vmyarJZb/qSh62"
  916. b"C8oIdxqm/BtvkGDza+On/Vfv2py7/0LV7VH+qR6a+bkKUbHXt5/SG187d+nps1a5PJfMO"
  917. b"i11dWcUe1HjwaW3Q5RHXn9LmcHy+tW9YcKf0768XVB1t3R0bKrzs5t9P+6r7rZ99svH10"
  918. b"+Q6F/o8tf1fO/32y+fWa14eifd+WxUy0jcxYH7N9/tUvmnUZL74pW32qLeuRU+ZwYGASa"
  919. b"GBgUWBgxM90ayy3VdmykkGDgYErJbEkERydFVWQmCMQo8aWZvAY/WteFRHFwMCYqXTPjI"
  920. b"lBkVEMGLsl+k8XP1D/z+gXyyDOvUemlnHqAVkvu0rRQ2fUFodkN3mtU9uwhqk8V+TqPEE"
  921. b"Nc7fzoQ4n71lqRs/7kbbT0+qOZuKH4r8mjzsc1k/YkCHN8Pjg48fbpE+teHa96LNcfu0V"
  922. b"5n2/Z2xa2KDvaCOx8cqBFxc514uZ3TmadXS+6cpzU7wSzq5SWfapJOD9n6wLXSwtlgxZh"
  923. b"xITzWW7buhx/bb291RcVlEfeC9K5hlrqunSzIMSZT7/Nqgc/qMvMNW227WI8ezB8mVuZh"
  924. b"0hERJSvysfburr4Dx0I9BW57UwR4+e1gxu49PcEt8sbK18Xpvt//Hj5UYm+Zc25q+T4xl"
  925. b"rJvxfVnh80oadq57OZxPaU1bbztv1yF365W4t45Yr+XrFzov237GVY1Zgf7NvE4+W2SuR"
  926. b"lQtLauR1TQ/mbOiIONYya6tU1jPGpWfk/i1+ttiXe3ZO14n0YOWggndznjGlGLyfVbBC6"
  927. b"MRP5aMM7aCco/s7sZqB8RlTQwADw8rnuT/sDHi7mUASjJFRAAbWwNLiAwAA"
  928. )
  929. def run_cmd(self, vm, cmd, user="root"):
  930. try:
  931. self.loop.run_until_complete(vm.run_for_stdio(cmd))
  932. except subprocess.CalledProcessError as e:
  933. return e.returncode
  934. return 0
  935. def setUp(self):
  936. if not self.template.count('debian') and \
  937. not self.template.count('fedora'):
  938. self.skipTest("Template {} not supported by this test".format(
  939. self.template))
  940. super(VmUpdatesMixin, self).setUp()
  941. self.update_cmd = None
  942. if self.template.count("debian"):
  943. self.update_cmd = "set -o pipefail; apt-get update 2>&1 | " \
  944. "{ ! grep '^W:\|^E:'; }"
  945. self.install_cmd = "apt-get install -y {}"
  946. self.install_test_cmd = "dpkg -l {}"
  947. self.exit_code_ok = [0]
  948. elif self.template.count("fedora"):
  949. cmd = "yum"
  950. try:
  951. # assume template name in form "fedora-XX-suffix"
  952. if int(self.template.split("-")[1]) > 21:
  953. cmd = "dnf"
  954. except ValueError:
  955. pass
  956. self.update_cmd = "{cmd} clean all; {cmd} check-update".format(
  957. cmd=cmd)
  958. self.install_cmd = cmd + " install -y {}"
  959. self.install_test_cmd = "rpm -q {}"
  960. self.exit_code_ok = [0, 100]
  961. self.init_default_template(self.template)
  962. self.init_networking()
  963. self.testvm1 = self.app.add_new_vm(
  964. qubes.vm.appvm.AppVM,
  965. name=self.make_vm_name('vm1'),
  966. label='red')
  967. self.loop.run_until_complete(self.testvm1.create_on_disk())
  968. def test_000_simple_update(self):
  969. self.app.save()
  970. # reload the VM to have all the properties properly set (especially
  971. # default netvm)
  972. self.testvm1 = self.app.domains[self.testvm1.qid]
  973. self.loop.run_until_complete(self.testvm1.start())
  974. p = self.loop.run_until_complete(
  975. self.testvm1.run(self.update_cmd, user='root',
  976. stdout=subprocess.PIPE, stderr=subprocess.PIPE))
  977. (stdout, stderr) = self.loop.run_until_complete(p.communicate())
  978. self.assertIn(p.returncode, self.exit_code_ok,
  979. '{}: {}\n{}'.format(self.update_cmd, stdout, stderr))
  980. def create_repo_apt(self):
  981. pkg_file_name = "test-pkg_1.0-1_amd64.deb"
  982. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  983. mkdir /tmp/apt-repo \
  984. && cd /tmp/apt-repo \
  985. && base64 -d | zcat > {}
  986. '''.format(pkg_file_name),
  987. input=self.DEB_PACKAGE_GZIP_BASE64))
  988. # do not assume dpkg-scanpackage installed
  989. packages_path = "dists/test/main/binary-amd64/Packages"
  990. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  991. mkdir -p /tmp/apt-repo/dists/test/main/binary-amd64 \
  992. && cd /tmp/apt-repo \
  993. && cat > {packages} \
  994. && echo MD5sum: $(openssl md5 -r {pkg} | cut -f 1 -d ' ') \
  995. >> {packages} \
  996. && echo SHA1: $(openssl sha1 -r {pkg} | cut -f 1 -d ' ') \
  997. >> {packages} \
  998. && echo SHA256: $(openssl sha256 -r {pkg} | cut -f 1 -d ' ') \
  999. >> {packages} \
  1000. && gzip < {packages} > {packages}.gz
  1001. '''.format(pkg=pkg_file_name, packages=packages_path),
  1002. input='''\
  1003. Package: test-pkg
  1004. Version: 1.0-1
  1005. Architecture: amd64
  1006. Maintainer: unknown <user@host>
  1007. Installed-Size: 25
  1008. Filename: {pkg}
  1009. Size: 994
  1010. Section: unknown
  1011. Priority: optional
  1012. Description: Test package'''.format(pkg=pkg_file_name).encode('utf-8')))
  1013. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1014. mkdir -p /tmp/apt-repo/dists/test \
  1015. && cd /tmp/apt-repo/dists/test \
  1016. && cat > Release \
  1017. && echo '' $(sha256sum {p} | cut -f 1 -d ' ') $(stat -c %s {p}) {p}\
  1018. >> Release \
  1019. && echo '' $(sha256sum {z} | cut -f 1 -d ' ') $(stat -c %s {z}) {z}\
  1020. >> Release
  1021. '''.format(p='main/binary-amd64/Packages',
  1022. z='main/binary-amd64/Packages.gz'),
  1023. input=b'''\
  1024. Label: Test repo
  1025. Suite: test
  1026. Codename: test
  1027. Date: Tue, 27 Oct 2015 03:22:09 UTC
  1028. Architectures: amd64
  1029. Components: main
  1030. SHA256:
  1031. '''))
  1032. def create_repo_yum(self):
  1033. pkg_file_name = "test-pkg-1.0-1.fc21.x86_64.rpm"
  1034. self.loop.run_until_complete(self.netvm_repo.run_for_stdio('''
  1035. mkdir /tmp/yum-repo \
  1036. && cd /tmp/yum-repo \
  1037. && base64 -d | zcat > {}
  1038. '''.format(pkg_file_name), input=self.RPM_PACKAGE_GZIP_BASE64))
  1039. # createrepo is installed by default in Fedora template
  1040. self.loop.run_until_complete(self.netvm_repo.run_for_stdio(
  1041. 'createrepo /tmp/yum-repo'))
  1042. def create_repo_and_serve(self):
  1043. if self.template.count("debian") or self.template.count("whonix"):
  1044. self.create_repo_apt()
  1045. self.loop.run_until_complete(self.netvm_repo.run(
  1046. 'cd /tmp/apt-repo && python -m SimpleHTTPServer 8080',
  1047. stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
  1048. elif self.template.count("fedora"):
  1049. self.create_repo_yum()
  1050. self.loop.run_until_complete(self.netvm_repo.run(
  1051. 'cd /tmp/yum-repo && python -m SimpleHTTPServer 8080',
  1052. stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
  1053. else:
  1054. # not reachable...
  1055. self.skipTest("Template {} not supported by this test".format(
  1056. self.template))
  1057. def configure_test_repo(self):
  1058. """
  1059. Configure test repository in test-vm and disable rest of them.
  1060. The critical part is to use "localhost" - this will work only when
  1061. accessed through update proxy and this is exactly what we want to
  1062. test here.
  1063. """
  1064. if self.template.count("debian") or self.template.count("whonix"):
  1065. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  1066. "rm -f /etc/apt/sources.list.d/* &&"
  1067. "echo 'deb [trusted=yes] http://localhost:8080 test main' "
  1068. "> /etc/apt/sources.list",
  1069. user="root"))
  1070. elif self.template.count("fedora"):
  1071. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  1072. "rm -f /etc/yum.repos.d/*.repo &&"
  1073. "echo '[test]' > /etc/yum.repos.d/test.repo &&"
  1074. "echo 'name=Test repo' >> /etc/yum.repos.d/test.repo &&"
  1075. "echo 'gpgcheck=0' >> /etc/yum.repos.d/test.repo &&"
  1076. "echo 'baseurl=http://localhost:8080/'"
  1077. " >> /etc/yum.repos.d/test.repo",
  1078. user="root"
  1079. ))
  1080. else:
  1081. # not reachable...
  1082. self.skipTest("Template {} not supported by this test".format(
  1083. self.template))
  1084. def test_010_update_via_proxy(self):
  1085. """
  1086. Test both whether updates proxy works and whether is actually used by the VM
  1087. """
  1088. if self.template.count("minimal"):
  1089. self.skipTest("Template {} not supported by this test".format(
  1090. self.template))
  1091. self.netvm_repo = self.app.add_new_vm(
  1092. qubes.vm.appvm.AppVM,
  1093. name=self.make_vm_name('net'),
  1094. label='red')
  1095. self.netvm_repo.provides_network = True
  1096. self.loop.run_until_complete(self.netvm_repo.create_on_disk())
  1097. self.testvm1.netvm = self.netvm_repo
  1098. self.netvm_repo.features['service.qubes-updates-proxy'] = True
  1099. # TODO: consider also adding a test for the template itself
  1100. self.testvm1.features['service.updates-proxy-setup'] = True
  1101. self.app.save()
  1102. # Setup test repo
  1103. self.loop.run_until_complete(self.netvm_repo.start())
  1104. self.create_repo_and_serve()
  1105. # Configure local repo
  1106. self.loop.run_until_complete(self.testvm1.start())
  1107. self.configure_test_repo()
  1108. with self.qrexec_policy('qubes.UpdatesProxy', self.testvm1,
  1109. '$default', action='allow,target=' + self.netvm_repo.name):
  1110. # update repository metadata
  1111. p = self.loop.run_until_complete(self.testvm1.run(
  1112. self.update_cmd, user='root', stdout=subprocess.PIPE,
  1113. stderr=subprocess.PIPE))
  1114. (stdout, stderr) = self.loop.run_until_complete(p.communicate())
  1115. self.assertIn(self.loop.run_until_complete(p.wait()), self.exit_code_ok,
  1116. '{}: {}\n{}'.format(self.update_cmd, stdout, stderr))
  1117. # install test package
  1118. p = self.loop.run_until_complete(self.testvm1.run(
  1119. self.install_cmd.format('test-pkg'), user='root',
  1120. stdout=subprocess.PIPE, stderr=subprocess.PIPE))
  1121. (stdout, stderr) = self.loop.run_until_complete(p.communicate())
  1122. self.assertIn(self.loop.run_until_complete(p.wait()), self.exit_code_ok,
  1123. '{}: {}\n{}'.format(self.update_cmd, stdout, stderr))
  1124. # verify if it was really installed
  1125. p = self.loop.run_until_complete(self.testvm1.run(
  1126. self.install_test_cmd.format('test-pkg'), user='root',
  1127. stdout=subprocess.PIPE, stderr=subprocess.PIPE))
  1128. (stdout, stderr) = self.loop.run_until_complete(p.communicate())
  1129. self.assertIn(self.loop.run_until_complete(p.wait()), self.exit_code_ok,
  1130. '{}: {}\n{}'.format(self.update_cmd, stdout, stderr))
  1131. def load_tests(loader, tests, pattern):
  1132. tests.addTests(loader.loadTestsFromNames(
  1133. qubes.tests.create_testcases_for_templates('VmNetworking',
  1134. VmNetworkingMixin, qubes.tests.SystemTestCase,
  1135. module=sys.modules[__name__])))
  1136. tests.addTests(loader.loadTestsFromNames(
  1137. qubes.tests.create_testcases_for_templates('VmIPv6Networking',
  1138. VmIPv6NetworkingMixin, qubes.tests.SystemTestCase,
  1139. module=sys.modules[__name__])))
  1140. tests.addTests(loader.loadTestsFromNames(
  1141. qubes.tests.create_testcases_for_templates('VmUpdates',
  1142. VmUpdatesMixin, qubes.tests.SystemTestCase,
  1143. module=sys.modules[__name__])))
  1144. return tests