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",
17 def persistent_volumes_mountpoints
18 @vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
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})
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)
31 Given /^I unplug USB drive "([^"]+)"$/ do |name|
32 next if @skip_steps_while_restoring_background
33 @vm.unplug_drive(name)
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)
41 class ISOHybridUpgradeNotSupported < StandardError
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')
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
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
64 @screen.wait('USBCreateLiveUSBConfirmWindow.png', 10)
65 @screen.wait_and_click('USBCreateLiveUSBConfirmYes.png', 10)
66 @screen.wait('USBInstallationComplete.png', 60*60)
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)
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)
83 When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
84 next if @skip_steps_while_restoring_background
86 step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
87 rescue ISOHybridUpgradeNotSupported
88 # this is what we expect
90 raise "The USB installer should not succeed"
94 When /^I am suggested to do a "Clone & Install"$/ do
95 next if @skip_steps_while_restoring_background
96 @screen.find("USBSuggestsInstall.png")
99 def shared_iso_dir_on_guest
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
129 @screen.type(Sikuli::Key.SPACE + Sikuli::Key.TAB)
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'")
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 " +
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}")
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/")
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}"
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}"
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}")
261 "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
263 @vm.execute("umount #{mount_dir}")
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))
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?
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")
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)
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
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]
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(" ")
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 " +
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}'")
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'")
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")
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'")
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}'")
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}")
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}")
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}")
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}")
468 Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
469 next if @skip_steps_while_restoring_background
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)
492 @screen.wait("PersistenceWizardDone.png", 120)