Start Iceweasel via the Applications menu.
[tails-test.git] / features / step_definitions / usb.rb
blobe80c93d10627492e79e3c6a4edfce3907efa79b3
1 def persistent_mounts
2   {
3     "cups-configuration" => "/etc/cups",
4     "nm-system-connections" => "/etc/NetworkManager/system-connections",
5     "claws-mail" => "/home/#{$live_user}/.claws-mail",
6     "gnome-keyrings" => "/home/#{$live_user}/.gnome2/keyrings",
7     "gnupg" => "/home/#{$live_user}/.gnupg",
8     "bookmarks" => "/home/#{$live_user}/.mozilla/firefox/bookmarks",
9     "pidgin" => "/home/#{$live_user}/.purple",
10     "openssh-client" => "/home/#{$live_user}/.ssh",
11     "Persistent" => "/home/#{$live_user}/Persistent",
12     "apt/cache" => "/var/cache/apt/archives",
13     "apt/lists" => "/var/lib/apt/lists",
14   }
15 end
17 def persistent_volumes_mountpoints
18   @vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
19 end
21 Given /^I create a new (\d+) ([[:alpha:]]+) USB drive named "([^"]+)"$/ do |size, unit, name|
22   next if @skip_steps_while_restoring_background
23   @vm.storage.create_new_disk(name, {:size => size, :unit => unit})
24 end
26 Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
27   next if @skip_steps_while_restoring_background
28   @vm.storage.clone_to_new_disk(from, to)
29 end
31 Given /^I unplug USB drive "([^"]+)"$/ do |name|
32   next if @skip_steps_while_restoring_background
33   @vm.unplug_drive(name)
34 end
36 Given /^the computer is set to boot from the old Tails DVD$/ do
37   next if @skip_steps_while_restoring_background
38   @vm.set_cdrom_boot($old_tails_iso)
39 end
41 class ISOHybridUpgradeNotSupported < StandardError
42 end
44 def usb_install_helper(name)
45   @screen.wait('USBCreateLiveUSB.png', 10)
47   # Here we'd like to select USB drive using #{name}, but Sikuli's
48   # OCR seems to be too unreliable.
49 #  @screen.wait('USBTargetDevice.png', 10)
50 #  match = @screen.find('USBTargetDevice.png')
51 #  region_x = match.x
52 #  region_y = match.y + match.h
53 #  region_w = match.w*3
54 #  region_h = match.h*2
55 #  ocr = Sikuli::Region.new(region_x, region_y, region_w, region_h).text
56 #  STDERR.puts ocr
57 #  # Unfortunately this results in almost garbage, like "|]dev/sdm"
58 #  # when it should be /dev/sda1
60   @screen.wait_and_click('USBCreateLiveUSB.png', 10)
61   if @screen.exists("USBSuggestsInstall.png")
62     raise ISOHybridUpgradeNotSupported
63   end
64   @screen.wait('USBCreateLiveUSBConfirmWindow.png', 10)
65   @screen.wait_and_click('USBCreateLiveUSBConfirmYes.png', 10)
66   @screen.wait('USBInstallationComplete.png', 60*60)
67 end
69 When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
70   next if @skip_steps_while_restoring_background
71   step "I run \"liveusb-creator-launcher\""
72   @screen.wait_and_click('USBCloneAndInstall.png', 30)
73   usb_install_helper(name)
74 end
76 When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
77   next if @skip_steps_while_restoring_background
78   step "I run \"liveusb-creator-launcher\""
79   @screen.wait_and_click('USBCloneAndUpgrade.png', 30)
80   usb_install_helper(name)
81 end
83 When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
84   next if @skip_steps_while_restoring_background
85   begin
86     step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
87   rescue ISOHybridUpgradeNotSupported
88     # this is what we expect
89   else
90     raise "The USB installer should not succeed"
91   end
92 end
94 When /^I am suggested to do a "Clone & Install"$/ do
95   next if @skip_steps_while_restoring_background
96   @screen.find("USBSuggestsInstall.png")
97 end
99 def shared_iso_dir_on_guest
100   "/tmp/shared_dir"
103 Given /^I setup a filesystem share containing the Tails ISO$/ do
104   next if @skip_steps_while_restoring_background
105   @vm.add_share(File.dirname($tails_iso), shared_iso_dir_on_guest)
108 When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
109   next if @skip_steps_while_restoring_background
110   step "I run \"liveusb-creator-launcher\""
111   @screen.wait_and_click('USBUpgradeFromISO.png', 10)
112   @screen.wait('USBUseLiveSystemISO.png', 10)
113   match = @screen.find('USBUseLiveSystemISO.png')
114   @screen.click(match.getCenter.offset(0, match.h*2))
115   @screen.wait('USBSelectISO.png', 10)
116   @screen.wait_and_click('GnomeFileDiagTypeFilename.png', 10)
117   iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
118   @screen.type(iso + Sikuli::Key.ENTER)
119   usb_install_helper(name)
122 Given /^I enable all persistence presets$/ do
123   next if @skip_steps_while_restoring_background
124   @screen.wait('PersistenceWizardPresets.png', 20)
125   # Mark first non-default persistence preset
126   @screen.type(Sikuli::Key.TAB*2)
127   # Check all non-default persistence presets
128   12.times do
129     @screen.type(Sikuli::Key.SPACE + Sikuli::Key.TAB)
130   end
131   @screen.wait_and_click('PersistenceWizardSave.png', 10)
132   @screen.wait('PersistenceWizardDone.png', 20)
133   @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
136 Given /^I create a persistent partition with password "([^"]+)"$/ do |pwd|
137   next if @skip_steps_while_restoring_background
138   step 'I run "gnome-terminal"'
139   @screen.wait_and_click('GnomeTerminalWindow.png', 20)
140   @screen.type('/usr/local/bin/tails-persistence-setup' + Sikuli::Key.ENTER)
141   @screen.wait('PersistenceWizardWindow.png', 40)
142   @screen.wait('PersistenceWizardStart.png', 20)
143   @screen.type(pwd + "\t" + pwd + Sikuli::Key.ENTER)
144   @screen.wait('PersistenceWizardPresets.png', 300)
145   step "I enable all persistence presets"
148 def check_part_integrity(name, dev, usage, type, scheme, label)
149   info = @vm.execute("udisks --show-info #{dev}").stdout
150   info_split = info.split("\n  partition:\n")
151   dev_info = info_split[0]
152   part_info = info_split[1]
153   assert(dev_info.match("^  usage: +#{usage}$"),
154          "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
155   assert(dev_info.match("^  type: +#{type}$"),
156          "Unexpected device field 'type' on USB drive '#{name}', '#{dev}'")
157   assert(part_info.match("^    scheme: +#{scheme}$"),
158          "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
159   assert(part_info.match("^    label: +#{label}$"),
160          "Unexpected partition label on USB drive '#{name}', '#{dev}'")
163 def tails_is_installed_helper(name, tails_root, loader)
164   dev = @vm.disk_dev(name) + "1"
165   check_part_integrity(name, dev, "filesystem", "vfat", "gpt", "Tails")
167   target_root = "/mnt/new"
168   @vm.execute("mkdir -p #{target_root}")
169   @vm.execute("mount #{dev} #{target_root}")
171   c = @vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'")
172   assert(c.success?,
173          "USB drive '#{name}' has differences in /live:\n#{c.stdout}")
175   syslinux_files = @vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
176   # We deal with these files separately
177   ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.sys"]
178   for f in syslinux_files - ignores do
179     c = @vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
180                     "'#{target_root}/syslinux/#{f}'")
181     assert(c.success?, "USB drive '#{name}' has differences in " +
182            "'/syslinux/#{f}'")
183   end
185   # The main .cfg is named differently vs isolinux
186   c = @vm.execute("diff -q '#{tails_root}/#{loader}/#{loader}.cfg' " +
187                   "'#{target_root}/syslinux/syslinux.cfg'")
188   assert(c.success?, "USB drive '#{name}' has differences in " +
189          "'/syslinux/syslinux.cfg'")
191   # We have to account for the different path vs isolinux
192   old_exithelp = @vm.execute("cat '#{tails_root}/#{loader}/exithelp.cfg'").stdout
193   new_exithelp = @vm.execute("cat '#{target_root}/syslinux/exithelp.cfg'").stdout
194   new_exithelp_undiffed = new_exithelp.sub("kernel /syslinux/vesamenu.c32",
195                                            "kernel /#{loader}/vesamenu.c32")
196   assert(new_exithelp_undiffed == old_exithelp,
197          "USB drive '#{name}' has unexpected differences in " +
198          "'/syslinux/exithelp.cfg'")
200   @vm.execute("umount #{target_root}")
201   @vm.execute("sync")
204 Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
205   next if @skip_steps_while_restoring_background
206   loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
207   tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader)
210 Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
211   next if @skip_steps_while_restoring_background
212   iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
213   iso_root = "/mnt/iso"
214   @vm.execute("mkdir -p #{iso_root}")
215   @vm.execute("mount -o loop #{iso} #{iso_root}")
216   tails_is_installed_helper(target_name, iso_root, "isolinux")
217   @vm.execute("umount #{iso_root}")
220 Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
221   next if @skip_steps_while_restoring_background
222   data_part_dev = @vm.disk_dev(name) + "2"
223   assert(!@vm.execute("test -b #{data_part_dev}").success?,
224          "USB drive #{name} has a partition '#{data_part_dev}'")
227 Then /^a Tails persistence partition with password "([^"]+)" exists on USB drive "([^"]+)"$/ do |pwd, name|
228   next if @skip_steps_while_restoring_background
229   dev = @vm.disk_dev(name) + "2"
230   check_part_integrity(name, dev, "crypto", "crypto_LUKS", "gpt", "TailsData")
232   # The LUKS container may already be opened, e.g. by udisks after
233   # we've run tails-persistence-setup.
234   c = @vm.execute("ls -1 /dev/mapper/")
235   if c.success?
236     for candidate in c.stdout.split("\n")
237       luks_info = @vm.execute("cryptsetup status #{candidate}")
238       if luks_info.success? and luks_info.stdout.match("^\s+device:\s+#{dev}$")
239         luks_dev = "/dev/mapper/#{candidate}"
240         break
241       end
242     end
243   end
244   if luks_dev.nil?
245     c = @vm.execute("echo #{pwd} | cryptsetup luksOpen #{dev} #{name}")
246     assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
247     luks_dev = "/dev/mapper/#{name}"
248   end
250   # Adapting check_part_integrity() seems like a bad idea so here goes
251   info = @vm.execute("udisks --show-info #{luks_dev}").stdout
252   assert info.match("^  cleartext luks device:$")
253   assert info.match("^  usage: +filesystem$")
254   assert info.match("^  type: +ext[34]$")
255   assert info.match("^  label: +TailsData$")
257   mount_dir = "/mnt/#{name}"
258   @vm.execute("mkdir -p #{mount_dir}")
259   c = @vm.execute("mount #{luks_dev} #{mount_dir}")
260   assert(c.success?,
261          "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
263   @vm.execute("umount #{mount_dir}")
264   @vm.execute("sync")
265   @vm.execute("cryptsetup luksClose #{name}")
268 Given /^I enable persistence with password "([^"]+)"$/ do |pwd|
269   next if @skip_steps_while_restoring_background
270   @screen.wait('TailsGreeterPersistence.png', 10)
271   @screen.type(Sikuli::Key.SPACE)
272   @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
273   match = @screen.find('TailsGreeterPersistencePassphrase.png')
274   @screen.click(match.getCenter.offset(match.w*2, match.h/2))
275   @screen.type(pwd)
278 def tails_persistence_enabled?
279   persistence_state_file = "/var/lib/live/config/tails.persistence"
280   return @vm.execute("test -e '#{persistence_state_file}'").success? &&
281          @vm.execute('. #{persistence_state_file} && ' +
282                      'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
285 Given /^persistence is enabled$/ do
286   next if @skip_steps_while_restoring_background
287   try_for(120, :msg => "Persistence is disabled") do
288     tails_persistence_enabled?
289   end
290   # Check that all persistent directories are mounted
291   mount = @vm.execute("mount").stdout.chomp
292   for _, dir in persistent_mounts do
293     assert(mount.include?("on #{dir} "),
294            "Persistent directory '#{dir}' is not mounted")
295   end
298 Given /^persistence is disabled$/ do
299   next if @skip_steps_while_restoring_background
300   assert(!tails_persistence_enabled?, "Persistence is enabled")
303 Given /^I enable read-only persistence with password "([^"]+)"$/ do |pwd|
304   step "I enable persistence with password \"#{pwd}\""
305   next if @skip_steps_while_restoring_background
306   @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
309 def boot_device
310   # Approach borrowed from
311   # config/chroot_local_includes/lib/live/config/998-permissions
312   boot_dev_id = @vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
313   boot_dev = @vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
314   return boot_dev
317 def boot_device_type
318   # Approach borrowed from
319   # config/chroot_local_includes/lib/live/config/998-permissions
320   boot_dev_info = @vm.execute("udevadm info --query=property --name='#{boot_device}'").stdout.chomp
321   boot_dev_type = (boot_dev_info.split("\n").select { |x| x.start_with? "ID_BUS=" })[0].split("=")[1]
322   return boot_dev_type
325 Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
326   next if @skip_steps_while_restoring_background
327   assert(boot_device_type == "usb",
328          "Got device type '#{boot_device_type}' while expecting 'usb'")
329   actual_dev = boot_device
330   # The boot partition differs between a "normal" install using the
331   # USB installer and isohybrid installations
332   expected_dev_normal = @vm.disk_dev(name) + "1"
333   expected_dev_isohybrid = @vm.disk_dev(name) + "4"
334   assert(actual_dev == expected_dev_normal ||
335          actual_dev == expected_dev_isohybrid,
336          "We are running from device #{actual_dev}, but for USB drive " +
337          "'#{name}' we expected to run from either device " +
338          "#{expected_dev_normal} (when installed via the USB installer) " +
339          "or #{expected_dev_normal} (when installed from an isohybrid)")
342 Then /^the boot device has safe access rights$/ do
343   next if @skip_steps_while_restoring_background
345   super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
346   devs = @vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
347   assert(devs.size > 0, "Could not determine boot device")
348   all_users = @vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
349   all_users_with_groups = all_users.collect do |user|
350     groups = @vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
351     [user, groups]
352   end
353   for dev in devs do
354     dev_owner = @vm.execute("stat -c %U #{dev}").stdout.chomp
355     dev_group = @vm.execute("stat -c %G #{dev}").stdout.chomp
356     dev_perms = @vm.execute("stat -c %a #{dev}").stdout.chomp
357     assert(dev_owner == "root",
358            "Boot device '#{dev}' owned by user '#{dev_owner}', expected 'root'")
359     assert(dev_group == "disk" || dev_group == "root",
360            "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
361            "'disk' or 'root'.")
362     assert(dev_perms == "1660",
363            "Boot device '#{dev}' has permissions '#{dev_perms}', expected '660'")
364     for user, groups in all_users_with_groups do
365       next if user == "root"
366       assert(!(groups.include?(dev_group)),
367              "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
368              "owns boot device '#{dev}'")
369     end
370   end
372   info = @vm.execute("udisks --show-info #{super_boot_dev}").stdout
373   assert(info.match("^  system internal: +1$"),
374          "Boot device '#{super_boot_dev}' is not system internal for udisks")
377 Then /^persistent filesystems have safe access rights$/ do
378   persistent_volumes_mountpoints.each do |mountpoint|
379     fs_owner = @vm.execute("stat -c %U #{mountpoint}").stdout.chomp
380     fs_group = @vm.execute("stat -c %G #{mountpoint}").stdout.chomp
381     fs_perms = @vm.execute("stat -c %a #{mountpoint}").stdout.chomp
382     assert(fs_owner == "root",
383            "Persistent filesystem '#{mountpoint}' owned by user '#{fs_owner}', expected 'root'")
384     assert(fs_group == "root",
385            "Persistent filesystem '#{mountpoint}' owned by group '#{fs_group}', expected 'root'")
386     assert(fs_perms == '775',
387            "Persistent filesystem '#{mountpoint}' has permissions '#{fs_perms}', expected '775'")
388   end
391 Then /^persistence configuration files have safe access rights$/ do
392   persistent_volumes_mountpoints.each do |mountpoint|
393     assert(@vm.execute("test -e #{mountpoint}/persistence.conf").success?,
394            "#{mountpoint}/persistence.conf does not exist, while it should")
395     assert(@vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
396            "#{mountpoint}/live-persistence.conf does exist, while it should not")
397     @vm.execute(
398       "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
399     ).stdout.chomp.split.each do |f|
400       file_owner = @vm.execute("stat -c %U '#{f}'").stdout.chomp
401       file_group = @vm.execute("stat -c %G '#{f}'").stdout.chomp
402       file_perms = @vm.execute("stat -c %a '#{f}'").stdout.chomp
403       assert(file_owner == "tails-persistence-setup",
404              "'#{f}' is owned by user '#{file_owner}', expected 'tails-persistence-setup'")
405       assert(file_group == "tails-persistence-setup",
406              "'#{f}' is owned by group '#{file_group}', expected 'tails-persistence-setup'")
407       assert(file_perms == "600",
408              "'#{f}' has permissions '#{file_perms}', expected '600'")
409     end
410   end
413 Then /^persistent directories have safe access rights$/ do
414   next if @skip_steps_while_restoring_background
415   expected_perms = "700"
416   persistent_volumes_mountpoints.each do |mountpoint|
417     # We also want to check that dotfiles' source has safe permissions
418     all_persistent_dirs = persistent_mounts.clone
419     all_persistent_dirs["dotfiles"] = "/home/#{$live_user}/"
420     persistent_mounts.each do |src, dest|
421       next unless dest.start_with?("/home/#{$live_user}/")
422       f = "#{mountpoint}/#{src}"
423       next unless @vm.execute("test -d #{f}").success?
424       file_perms = @vm.execute("stat -c %a '#{f}'").stdout.chomp
425       assert(file_perms == expected_perms,
426              "'#{f}' has permissions '#{file_perms}', expected '#{expected_perms}'")
427     end
428   end
431 When /^I write some files expected to persist$/ do
432   next if @skip_steps_while_restoring_background
433   persistent_mounts.each do |_, dir|
434     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
435     assert(@vm.execute("touch #{dir}/XXX_persist", user=owner).success?,
436            "Could not create file in persistent directory #{dir}")
437   end
440 When /^I remove some files expected to persist$/ do
441   next if @skip_steps_while_restoring_background
442   persistent_mounts.each do |_, dir|
443     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
444     assert(@vm.execute("rm #{dir}/XXX_persist", user=owner).success?,
445            "Could not remove file in persistent directory #{dir}")
446   end
449 When /^I write some files not expected to persist$/ do
450   next if @skip_steps_while_restoring_background
451   persistent_mounts.each do |_, dir|
452     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
453     assert(@vm.execute("touch #{dir}/XXX_gone", user=owner).success?,
454            "Could not create file in persistent directory #{dir}")
455   end
458 Then /^the expected persistent files are present in the filesystem$/ do
459   next if @skip_steps_while_restoring_background
460   persistent_mounts.each do |_, dir|
461     assert(@vm.execute("test -e #{dir}/XXX_persist").success?,
462            "Could not find expected file in persistent directory #{dir}")
463     assert(!@vm.execute("test -e #{dir}/XXX_gone").success?,
464            "Found file that should not have persisted in persistent directory #{dir}")
465   end
468 Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
469   next if @skip_steps_while_restoring_background
470   step "a computer"
471   step "the computer is set to boot from USB drive \"#{name}\""
472   step "the network is unplugged"
473   step "I start the computer"
474   step "the computer boots Tails"
475   step "I enable read-only persistence with password \"asdf\""
476   step "I log in to a new session"
477   step "persistence is enabled"
478   step "GNOME has started"
479   step "all notifications have disappeared"
480   step "the expected persistent files are present in the filesystem"
481   step "I shutdown Tails and wait for the computer to power off"
484 When /^I delete the persistent partition$/ do
485   next if @skip_steps_while_restoring_background
486   step 'I run "gnome-terminal"'
487   @screen.wait_and_click('GnomeTerminalWindow.png', 20)
488   @screen.type('/usr/local/bin/tails-delete-persistent-volume' + Sikuli::Key.ENTER)
489   @screen.wait("PersistenceWizardWindow.png", 40)
490   @screen.wait("PersistenceWizardDeletionStart.png", 20)
491   @screen.type(" ")
492   @screen.wait("PersistenceWizardDone.png", 120)