Update changelog for 0.21~rc1.
[tails-test.git] / features / step_definitions / usb.rb
blob66fcfb20ee6a62bb980068db2c5d05985620ec35
1 def persistent_dirs
2   ["/home/#{$live_user}/.claws-mail",
3    "/home/#{$live_user}/.gconf/system/networking/connections",
4    "/home/#{$live_user}/.gnome2/keyrings",
5    "/home/#{$live_user}/.gnupg",
6    "/home/#{$live_user}/.mozilla/firefox/bookmarks",
7    "/home/#{$live_user}/.purple",
8    "/home/#{$live_user}/.ssh",
9    "/home/#{$live_user}/Persistent",
10    "/var/cache/apt/archives",
11    "/var/lib/apt/lists"]
12 end
14 def persistent_volumes_mountpoints
15   @vm.execute("ls -1 /live/persistence/*_unlocked/").stdout.chomp.split
16 end
18 Given /^I create a new (\d+) ([[:alpha:]]+) USB drive named "([^"]+)"$/ do |size, unit, name|
19   next if @skip_steps_while_restoring_background
20   @vm.storage.create_new_disk(name, {:size => size, :unit => unit})
21 end
23 Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
24   next if @skip_steps_while_restoring_background
25   @vm.storage.clone_to_new_disk(from, to)
26 end
28 Given /^I unplug USB drive "([^"]+)"$/ do |name|
29   next if @skip_steps_while_restoring_background
30   @vm.unplug_drive(name)
31 end
33 Given /^the computer is set to boot from the old Tails DVD$/ do
34   next if @skip_steps_while_restoring_background
35   @vm.set_cdrom_boot($old_tails_iso)
36 end
38 class ISOHybridUpgradeNotSupported < StandardError
39 end
41 def usb_install_helper(name)
42   @screen.wait('USBCreateLiveUSB.png', 10)
44   # Here we'd like to select USB drive using #{name}, but Sikuli's
45   # OCR seems to be too unreliable.
46 #  @screen.wait('USBTargetDevice.png', 10)
47 #  match = @screen.find('USBTargetDevice.png')
48 #  region_x = match.x
49 #  region_y = match.y + match.height
50 #  region_w = match.width*3
51 #  region_h = match.height*2
52 #  ocr = Sikuli::Region.new(region_x, region_y, region_w, region_h).text
53 #  STDERR.puts ocr
54 #  # Unfortunately this results in almost garbage, like "|]dev/sdm"
55 #  # when it should be /dev/sda1
57   @screen.wait_and_click('USBCreateLiveUSB.png', 10)
58   begin
59     if @screen.find("USBSuggestsInstall.png")
60       raise ISOHybridUpgradeNotSupported
61     end
62   rescue Sikuli::ImageNotFound
63     # we didn't get the warning, so we can proceed with the install
64   end
65 #  @screen.hide_cursor
66   @screen.wait_and_click('USBCreateLiveUSBNext.png', 10)
67 #  @screen.hide_cursor
68   @screen.wait('USBInstallationComplete.png', 60*60)
69   @screen.type(Sikuli::KEY_RETURN)
70   @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
71 end
73 When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
74   next if @skip_steps_while_restoring_background
75   step "I run \"liveusb-creator-launcher\""
76   @screen.wait_and_click('USBCloneAndInstall.png', 30)
77   usb_install_helper(name)
78 end
80 When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
81   next if @skip_steps_while_restoring_background
82   step "I run \"liveusb-creator-launcher\""
83   @screen.wait_and_click('USBCloneAndUpgrade.png', 30)
84   usb_install_helper(name)
85 end
87 When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
88   next if @skip_steps_while_restoring_background
89   begin
90     step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
91   rescue ISOHybridUpgradeNotSupported
92     # this is what we expect
93   else
94     raise "The USB installer should not succeed"
95   end
96 end
98 When /^I am suggested to do a "Clone & Upgrade"$/ do
99   next if @skip_steps_while_restoring_background
100   @screen.find("USBSuggestsInstall.png")
103 def shared_iso_dir_on_guest
104   "/tmp/shared_dir"
107 Given /^I setup a filesystem share containing the Tails ISO$/ do
108   next if @skip_steps_while_restoring_background
109   @vm.add_share(File.dirname($tails_iso), shared_iso_dir_on_guest)
112 When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
113   next if @skip_steps_while_restoring_background
114   step "I run \"liveusb-creator-launcher\""
115   @screen.wait_and_click('USBUpgradeFromISO.png', 10)
116   @screen.wait('USBUseLiveSystemISO.png', 10)
117   match = @screen.find('USBUseLiveSystemISO.png')
118   pos_x = match.x + match.width/2
119   pos_y = match.y + match.height*2
120   @screen.click(pos_x, pos_y)
121   @screen.wait('USBSelectISO.png', 10)
122   @screen.wait_and_click('GnomeFileDiagTypeFilename.png', 10)
123   iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
124   @screen.type(iso + Sikuli::KEY_RETURN)
125   usb_install_helper(name)
128 Given /^I enable all persistence presets$/ do
129   next if @skip_steps_while_restoring_background
130   @screen.wait('PersistenceWizardPresets.png', 20)
131   # Mark first non-default persistence preset
132   @screen.type("\t\t")
133   # Check all non-default persistence presets
134   10.times do
135     @screen.type(" \t")
136   end
137   @screen.wait_and_click('PersistenceWizardSave.png', 10)
138   @screen.wait('PersistenceWizardDone.png', 20)
139   @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
142 Given /^I create a persistent partition with password "([^"]+)"$/ do |pwd|
143   next if @skip_steps_while_restoring_background
144   step "I run \"tails-persistence-setup\""
145   @screen.wait('PersistenceWizardWindow.png', 20)
146   @screen.wait('PersistenceWizardStart.png', 20)
147   @screen.type(pwd + "\t" + pwd + Sikuli::KEY_RETURN)
148   @screen.wait('PersistenceWizardPresets.png', 300)
149   step "I enable all persistence presets"
152 def check_part_integrity(name, dev, usage, type, scheme, label)
153   info = @vm.execute("udisks --show-info #{dev}").stdout
154   info_split = info.split("\n  partition:\n")
155   dev_info = info_split[0]
156   part_info = info_split[1]
157   assert(dev_info.match("^  usage: +#{usage}$"),
158          "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
159   assert(dev_info.match("^  type: +#{type}$"),
160          "Unexpected device field 'type' on USB drive '#{name}', '#{dev}'")
161   assert(part_info.match("^    scheme: +#{scheme}$"),
162          "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
163   assert(part_info.match("^    label: +#{label}$"),
164          "Unexpected partition label on USB drive '#{name}', '#{dev}'")
167 Then /^Tails is installed on USB drive "([^"]+)"$/ do |name|
168   next if @skip_steps_while_restoring_background
169   dev = @vm.disk_dev(name) + "1"
170   check_part_integrity(name, dev, "filesystem", "vfat", "gpt", "Tails")
172   old_root = "/lib/live/mount/medium"
173   new_root = "/mnt/new"
174   @vm.execute("mkdir -p #{new_root}")
175   @vm.execute("mount #{dev} #{new_root}")
177   c = @vm.execute("diff -qr '#{old_root}/live' '#{new_root}/live'")
178   assert(c.success?,
179          "USB drive '#{name}' has differences in /live:\n#{c.stdout}")
181   loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
182   syslinux_files = @vm.execute("ls -1 #{new_root}/syslinux").stdout.chomp.split
183   # We deal with these files separately
184   ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.sys"]
185   for f in syslinux_files - ignores do
186     c = @vm.execute("diff -q '#{old_root}/#{loader}/#{f}' " +
187                     "'#{new_root}/syslinux/#{f}'")
188     assert(c.success?, "USB drive '#{name}' has differences in " +
189            "'/syslinux/#{f}'")
190   end
192   # The main .cfg is named differently vs isolinux
193   c = @vm.execute("diff -q '#{old_root}/#{loader}/#{loader}.cfg' " +
194                   "'#{new_root}/syslinux/syslinux.cfg'")
195   assert(c.success?, "USB drive '#{name}' has differences in " +
196          "'/syslinux/syslinux.cfg'")
198   # We have to account for the different path vs isolinux
199   old_exithelp = @vm.execute("cat '#{old_root}/#{loader}/exithelp.cfg'").stdout
200   new_exithelp = @vm.execute("cat '#{new_root}/syslinux/exithelp.cfg'").stdout
201   new_exithelp_undiffed = new_exithelp.sub("kernel /syslinux/vesamenu.c32",
202                                            "kernel /#{loader}/vesamenu.c32")
203   assert(new_exithelp_undiffed == old_exithelp,
204          "USB drive '#{name}' has unexpected differences in " +
205          "'/syslinux/exithelp.cfg'")
207   @vm.execute("umount #{new_root}")
208   @vm.execute("sync")
211 Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
212   next if @skip_steps_while_restoring_background
213   data_part_dev = @vm.disk_dev(name) + "2"
214   assert(!@vm.execute("test -b #{data_part_dev}").success?,
215          "USB drive #{name} has a partition '#{data_part_dev}'")
218 Then /^a Tails persistence partition with password "([^"]+)" exists on USB drive "([^"]+)"$/ do |pwd, name|
219   next if @skip_steps_while_restoring_background
220   dev = @vm.disk_dev(name) + "2"
221   check_part_integrity(name, dev, "crypto", "crypto_LUKS", "gpt", "TailsData")
223   c = @vm.execute("echo #{pwd} | cryptsetup luksOpen #{dev} #{name}")
224   assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
225   luks_dev = "/dev/mapper/#{name}"
227   # Adapting check_part_integrity() seems like a bad idea so here goes
228   info = @vm.execute("udisks --show-info #{luks_dev}").stdout
229   assert info.match("^  cleartext luks device:$")
230   assert info.match("^  usage: +filesystem$")
231   assert info.match("^  type: +ext[34]$")
232   assert info.match("^  label: +TailsData$")
234   mount_dir = "/mnt/#{name}"
235   @vm.execute("mkdir -p #{mount_dir}")
236   c = @vm.execute("mount #{luks_dev} #{mount_dir}")
237   assert(c.success?,
238          "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
240   @vm.execute("umount #{mount_dir}")
241   @vm.execute("sync")
242   @vm.execute("cryptsetup luksClose #{name}")
245 Given /^I enable persistence with password "([^"]+)"$/ do |pwd|
246   next if @skip_steps_while_restoring_background
247   match = @screen.find('TailsGreeterPersistence.png')
248   pos_x = match.x + match.width/2
249   # height*2 may seem odd, but we want to click the button below the
250   # match. This may even work accross different screen resolutions.
251   pos_y = match.y + match.height*2
252   @screen.click(pos_x, pos_y)
253   @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
254   match = @screen.find('TailsGreeterPersistencePassphrase.png')
255   pos_x = match.x + match.width*2
256   pos_y = match.y + match.height/2
257   @screen.click(pos_x, pos_y)
258   @screen.type(pwd)
261 Given /^persistence is not enabled$/ do
262   next if @skip_steps_while_restoring_background
263   data_part_dev = boot_device + "2"
264   assert(!@vm.execute("grep -q '^#{data_part_dev} ' /proc/mounts").success?,
265          "Partition '#{data_part_dev}' from the boot device is mounted")
268 Given /^I enable read-only persistence with password "([^"]+)"$/ do |pwd|
269   step "I enable persistence with password \"#{pwd}\""
270   next if @skip_steps_while_restoring_background
271   @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
274 Given /^persistence has been enabled$/ do
275   next if @skip_steps_while_restoring_background
276   try_for(120, :msg => "Some persistent dir was not mounted") {
277     mount = @vm.execute("mount").stdout.chomp
278     persistent_dirs.each do |dir|
279       if ! mount.include? "on #{dir} "
280         raise "persistent dir #{dir} missing"
281       end
282     end
283   }
286 def boot_device
287   # Approach borrowed from
288   # config/chroot_local_includes/lib/live/config/998-permissions
289   boot_dev_id = @vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
290   boot_dev = @vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
291   return boot_dev
294 def boot_device_type
295   # Approach borrowed from
296   # config/chroot_local_includes/lib/live/config/998-permissions
297   boot_dev_info = @vm.execute("udevadm info --query=property --name='#{boot_device}'").stdout.chomp
298   boot_dev_type = (boot_dev_info.split("\n").select { |x| x.start_with? "ID_BUS=" })[0].split("=")[1]
299   return boot_dev_type
302 Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
303   next if @skip_steps_while_restoring_background
304   assert(boot_device_type == "usb",
305          "Got device type '#{boot_device_type}' while expecting 'usb'")
306   actual_dev = boot_device
307   # The boot partition differs between a "normal" install using the
308   # USB installer and isohybrid installations
309   expected_dev_normal = @vm.disk_dev(name) + "1"
310   expected_dev_isohybrid = @vm.disk_dev(name) + "4"
311   assert(actual_dev == expected_dev_normal ||
312          actual_dev == expected_dev_isohybrid,
313          "We are running from device #{actual_dev}, but for USB drive " +
314          "'#{name}' we expected to run from either device " +
315          "#{expected_dev_normal} (when installed via the USB installer) " +
316          "or #{expected_dev_normal} (when installed from an isohybrid)")
319 Then /^the boot device has safe access rights$/ do
320   next if @skip_steps_while_restoring_background
322   super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
323   devs = @vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
324   assert(devs.size > 0, "Could not determine boot device")
325   all_users = @vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
326   all_users_with_groups = all_users.collect do |user|
327     groups = @vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
328     [user, groups]
329   end
330   for dev in devs do
331     dev_owner = @vm.execute("stat -c %U #{dev}").stdout.chomp
332     dev_group = @vm.execute("stat -c %G #{dev}").stdout.chomp
333     dev_perms = @vm.execute("stat -c %a #{dev}").stdout.chomp
334     assert(dev_owner == "root",
335            "Boot device '#{dev}' owned by user '#{dev_owner}', expected 'root'")
336     assert(dev_group == "disk" || dev_group == "root",
337            "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
338            "'disk' or 'root'.")
339     assert(dev_perms == "660",
340            "Boot device '#{dev}' has permissions '#{dev_perms}', expected '660'")
341     for user, groups in all_users_with_groups do
342       next if user == "root"
343       assert(!(groups.include?(dev_group)),
344              "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
345              "owns boot device '#{dev}'")
346     end
347   end
350 Then /^persistent filesystems have safe access rights$/ do
351   persistent_volumes_mountpoints.each do |mountpoint|
352     fs_owner = @vm.execute("stat -c %U #{mountpoint}").stdout.chomp
353     fs_group = @vm.execute("stat -c %G #{mountpoint}").stdout.chomp
354     fs_perms = @vm.execute("stat -c %a #{mountpoint}").stdout.chomp
355     assert(fs_owner == "root",
356            "Persistent filesystem '#{mountpoint}' owned by user '#{fs_owner}', expected 'root'")
357     assert(fs_group == "amnesia",
358            "Persistent filesystem '#{mountpoint}' owned by group '#{fs_group}', expected 'amnesia'")
359     assert(fs_perms == '1777',
360            "Persistent filesystem '#{mountpoint}' has permissions '#{fs_perms}', expected '1777'")
361   end
364 Then /^persistence configuration files have safe access rights$/ do
365   persistent_volumes_mountpoints.each do |mountpoint|
366     assert(@vm.execute("test -e #{mountpoint}/persistence.conf").success?,
367            "#{mountpoint}/persistence.conf does not exist, while it should")
368     assert(@vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
369            "#{mountpoint}/live-persistence.conf does exist, while it should not")
370     @vm.execute(
371       "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
372     ).stdout.chomp.split.each do |f|
373       file_owner = @vm.execute("stat -c %U '#{f}'").stdout.chomp
374       file_group = @vm.execute("stat -c %G '#{f}'").stdout.chomp
375       file_perms = @vm.execute("stat -c %a '#{f}'").stdout.chomp
376       assert(file_owner = "tails-persistence-setup",
377              "'#{f}' is owned by user '#{file_owner}', expected 'tails-persistence-setup'")
378       assert(file_group = "tails-persistence-setup",
379              "'#{f}' is owned by group '#{file_group}', expected 'tails-persistence-setup'")
380       assert(file_perms = "600",
381              "'#{f}' has permissions '#{file_perms}', expected '600'")
382     end
383   end
386 When /^I write some files expected to persist$/ do
387   next if @skip_steps_while_restoring_background
388   persistent_dirs.each do |dir|
389     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
390     assert(@vm.execute("touch #{dir}/XXX_persist", user=owner).success?,
391            "Could not create file in persistent directory #{dir}")
392   end
395 When /^I remove some files expected to persist$/ do
396   next if @skip_steps_while_restoring_background
397   persistent_dirs.each do |dir|
398     assert(@vm.execute("rm #{dir}/XXX_persist").success?,
399            "Could not remove file in persistent directory #{dir}")
400   end
403 When /^I write some files not expected to persist$/ do
404   next if @skip_steps_while_restoring_background
405   persistent_dirs.each do |dir|
406     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
407     assert(@vm.execute("touch #{dir}/XXX_gone", user=owner).success?,
408            "Could not create file in persistent directory #{dir}")
409   end
412 Then /^the expected persistent files are present in the filesystem$/ do
413   next if @skip_steps_while_restoring_background
414   persistent_dirs.each do |dir|
415     assert(@vm.execute("test -e #{dir}/XXX_persist").success?,
416            "Could not find expected file in persistent directory #{dir}")
417     assert(!@vm.execute("test -e #{dir}/XXX_gone").success?,
418            "Found file that should not have persisted in persistent directory #{dir}")
419   end
422 Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
423   next if @skip_steps_while_restoring_background
424   step "a computer"
425   step "the computer is set to boot from USB drive \"#{name}\""
426   step "the network is unplugged"
427   step "I start the computer"
428   step "the computer boots Tails"
429   step "I enable read-only persistence with password \"asdf\""
430   step "I log in to a new session"
431   step "persistence has been enabled"
432   step "GNOME has started"
433   step "I have closed all annoying notifications"
434   step "the expected persistent files are present in the filesystem"
435   step "I completely shutdown Tails"
438 When /^I delete the persistent partition$/ do
439   next if @skip_steps_while_restoring_background
440   step "I run \"tails-persistence-setup --step delete\""
441   @screen.wait("PersistenceWizardWindow.png", 20)
442   @screen.wait("PersistenceWizardDeletionStart.png", 20)
443   @screen.type(" ")
444   @screen.wait("PersistenceWizardDone.png", 120)