usb.rb 27.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
# Returns a hash that for each preset the running Tails is aware of
# maps the source to the destination.
def get_persistence_presets(skip_links = false)
  # Perl script that prints all persistence presets (one per line) on
  # the form: <mount_point>:<comma-separated-list-of-options>
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
Tails developers's avatar
Tails developers committed
10
11
  foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
    say $preset->destination, ":", join(",", @{$preset->options});
12
  }
13
14
15
16
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
17
  presets = $vm.execute_successfully("perl -E '#{script}'").stdout.chomp.split("\n")
18
19
20
21
22
23
24
25
26
  assert presets.size >= 10, "Got #{presets.size} persistence presets, " +
                             "which is too few"
  persistence_mapping = Hash.new
  for line in presets
    destination, options_str = line.split(":")
    options = options_str.split(",")
    is_link = options.include? "link"
    next if is_link and skip_links
    source_str = options.find { |option| /^source=/.match option }
27
28
29
30
31
32
33
    # If no source is given as an option, live-boot's persistence
    # feature defaults to the destination minus the initial "/".
    if source_str.nil?
      source = destination.partition("/").last
    else
      source = source_str.split("=")[1]
    end
34
35
36
37
38
39
40
41
42
43
44
    persistence_mapping[source] = destination
  end
  return persistence_mapping
end

def persistent_dirs
  get_persistence_presets
end

def persistent_mounts
  get_persistence_presets(true)
45
46
end

47
def persistent_volumes_mountpoints
48
  $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
49
50
end

51
52
53
54
55
56
57
58
def recover_from_upgrader_failure
    $vm.execute('killall tails-upgrade-frontend tails-upgrade-frontend-wrapper zenity')
    # Remove unnecessary sleep for retry
    $vm.execute_successfully('sed -i "/^sleep 30$/d" ' +
                             '/usr/local/bin/tails-upgrade-frontend-wrapper')
    $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
end

59
Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
60
  $vm.storage.clone_to_new_disk(from, to)
61
62
63
end

Given /^I unplug USB drive "([^"]+)"$/ do |name|
64
  $vm.unplug_drive(name)
65
66
end

67
Given /^the computer is set to boot from the old Tails DVD$/ do
68
  $vm.set_cdrom_boot(OLD_TAILS_ISO)
69
70
end

71
Given /^the computer is set to boot in UEFI mode$/ do
72
  $vm.set_os_loader('UEFI')
73
74
75
  @os_loader = 'UEFI'
end

76
class UpgradeNotSupported < StandardError
77
78
end

79
def usb_install_helper(name)
anonym's avatar
anonym committed
80
  @screen.wait('USBTailsLogo.png', 10)
81
82
  if @screen.exists("USBCannotUpgrade.png")
    raise UpgradeNotSupported
83
  end
84
85
86
87
88
89
  begin
    @screen.wait_and_click('USBCreateLiveUSB.png', 10)
    @screen.wait('USBCreateLiveUSBConfirmWindow.png', 10)
    @screen.wait_and_click('USBCreateLiveUSBConfirmYes.png', 10)
    @screen.wait('USBInstallationComplete.png', 30*60)
  rescue FindFailed => e
90
91
    path = $vm.execute_successfully('ls -1 /tmp/tails-installer-*').stdout.chomp
    debug_log("Tails Installer debug log:\n" + $vm.file_content(path))
92
93
    raise e
  end
94
95
end

96
When /^I start Tails Installer$/ do
97
  step 'I run "export DEBUG=1 ; tails-installer-launcher" in GNOME Terminal'
98
99
100
  @screen.wait('USBCloneAndInstall.png', 30)
end

101
102
When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
  step 'I start Tails Installer'
103
104
105
106
107
108
109
110
111
112
  case mode
  when 'Clone & Install'
    @screen.wait_and_click('USBCloneAndInstall.png', 10)
  when 'Clone & Upgrade'
    @screen.wait_and_click('USBCloneAndUpgrade.png', 10)
  when 'Upgrade from ISO'
    @screen.wait_and_click('USBUpgradeFromISO.png', 10)
  else
    raise "Unsupported mode '#{mode}'"
  end
113
114
end

115
116
Then /^Tails Installer detects that a device is too small$/ do
  @screen.wait('TailsInstallerTooSmallDevice.png', 10)
117
118
end

119
When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
120
  step 'I start Tails Installer in "Clone & Install" mode'
121
122
123
124
  usb_install_helper(name)
end

When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
125
  step 'I start Tails Installer in "Clone & Upgrade" mode'
126
127
128
  usb_install_helper(name)
end

129
130
131
When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
  begin
    step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
132
  rescue UpgradeNotSupported
133
134
135
136
137
138
139
140
141
    # this is what we expect
  else
    raise "The USB installer should not succeed"
  end
end

When /^I try to "Upgrade from ISO" USB drive "([^"]+)"$/ do |name|
  begin
    step "I do a \"Upgrade from ISO\" on USB drive \"#{name}\""
142
  rescue UpgradeNotSupported
143
144
145
146
147
148
    # this is what we expect
  else
    raise "The USB installer should not succeed"
  end
end

Tails developers's avatar
Tails developers committed
149
When /^I am suggested to do a "Clone & Install"$/ do
150
  @screen.find("USBCannotUpgrade.png")
151
152
end

153
154
When /^I am told that the destination device cannot be upgraded$/ do
  @screen.find("USBCannotUpgrade.png")
155
156
end

157
Given /^I setup a filesystem share containing the Tails ISO$/ do
anonym's avatar
anonym committed
158
159
  shared_iso_dir_on_host = "#{$config["TMPDIR"]}/shared_iso_dir"
  @shared_iso_dir_on_guest = "/tmp/shared_iso_dir"
160
161
162
  FileUtils.mkdir_p(shared_iso_dir_on_host)
  FileUtils.cp(TAILS_ISO, shared_iso_dir_on_host)
  add_after_scenario_hook { FileUtils.rm_r(shared_iso_dir_on_host) }
163
  $vm.add_share(shared_iso_dir_on_host, @shared_iso_dir_on_guest)
164
165
end

166
When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
167
  step 'I start Tails Installer in "Upgrade from ISO" mode'
168
169
  @screen.wait('USBUseLiveSystemISO.png', 10)
  match = @screen.find('USBUseLiveSystemISO.png')
170
  @screen.click(match.getCenter.offset(0, match.h*2))
171
  @screen.wait('USBSelectISO.png', 10)
172
  @screen.wait_and_click('GnomeFileDiagHome.png', 10)
173
174
  @screen.type("l", Sikuli::KeyModifier.CTRL)
  @screen.wait('GnomeFileDiagTypeFilename.png', 10)
175
  iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
176
177
  @screen.type(iso)
  @screen.wait_and_click('GnomeFileDiagOpenButton.png', 10)
178
179
180
181
182
  usb_install_helper(name)
end

Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
183
184
185
186
187
188
  # Select the "Persistent" folder preset, which is checked by default.
  @screen.type(Sikuli::Key.TAB)
  # Check all non-default persistence presets, i.e. all *after* the
  # "Persistent" folder, which are unchecked by default.
  (persistent_dirs.size - 1).times do
    @screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE)
189
  end
190
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
intrigeri's avatar
intrigeri committed
191
  @screen.wait('PersistenceWizardDone.png', 60)
192
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
193
194
end

195
When /^I disable the first persistence preset$/ do
196
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
197
198
199
200
201
202
203
  @screen.wait('PersistenceWizardPresets.png', 300)
  @screen.type(Sikuli::Key.SPACE)
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
  @screen.wait('PersistenceWizardDone.png', 30)
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
end

204
Given /^I create a persistent partition$/ do
205
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
206
  @screen.wait('PersistenceWizardStart.png', 60)
207
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
208
  @screen.wait('PersistenceWizardPresets.png', 300)
209
210
211
  step "I enable all persistence presets"
end

212
def check_disk_integrity(name, dev, scheme)
213
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
214
215
216
217
218
219
220
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.PartitionTable:\n")
  dev_info = info_split[0]
  part_table_info = info_split[1]
  assert(part_table_info.match("^    Type: +#{scheme}$"),
         "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
end

221
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
222
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
223
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
224
225
  dev_info = info_split[0]
  part_info = info_split[1]
226
  assert(dev_info.match("^    IdUsage: +#{usage}$"),
227
         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
228
229
230
  assert(dev_info.match("^    IdType: +#{fs_type}$"),
         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
  assert(part_info.match("^    Name: +#{part_label}$"),
231
         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
232
233
234
235
  if part_type
    assert(part_info.match("^    Type: +#{part_type}$"),
           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
  end
236
237
end

238
def tails_is_installed_helper(name, tails_root, loader)
239
  disk_dev = $vm.disk_dev(name)
240
241
  part_dev = disk_dev + "1"
  check_disk_integrity(name, disk_dev, "gpt")
242
243
244
  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
                       # EFI System Partition
                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
245

246
  target_root = "/mnt/new"
247
  $vm.execute("mkdir -p #{target_root}")
248
  $vm.execute("mount #{part_dev} #{target_root}")
249

250
  c = $vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'")
251
  assert(c.success?,
252
         "USB drive '#{name}' has differences in /live:\n#{c.stdout}\n#{c.stderr}")
253

254
  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
255
  # We deal with these files separately
256
  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
257
  for f in syslinux_files - ignores do
258
    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
259
                    "'#{target_root}/syslinux/#{f}'")
260
261
262
263
264
    assert(c.success?, "USB drive '#{name}' has differences in " +
           "'/syslinux/#{f}'")
  end

  # The main .cfg is named differently vs isolinux
265
  c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{loader}.cfg' " +
266
                  "'#{target_root}/syslinux/syslinux.cfg'")
267
268
269
  assert(c.success?, "USB drive '#{name}' has differences in " +
         "'/syslinux/syslinux.cfg'")

270
271
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
272
273
end

274
275
276
277
278
279
Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
  loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
  tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader)
end

Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
280
  iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
281
  iso_root = "/mnt/iso"
282
283
  $vm.execute("mkdir -p #{iso_root}")
  $vm.execute("mount -o loop #{iso} #{iso_root}")
Tails developers's avatar
Tails developers committed
284
  tails_is_installed_helper(target_name, iso_root, "isolinux")
285
  $vm.execute("umount #{iso_root}")
286
287
end

288
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
289
290
  data_part_dev = $vm.disk_dev(name) + "2"
  assert(!$vm.execute("test -b #{data_part_dev}").success?,
291
292
293
         "USB drive #{name} has a partition '#{data_part_dev}'")
end

294
Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
295
  dev = $vm.disk_dev(name) + "2"
296
  check_part_integrity(name, dev, "crypto", "crypto_LUKS", "TailsData")
297

298
299
  # The LUKS container may already be opened, e.g. by udisks after
  # we've run tails-persistence-setup.
300
  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
301
302
  if c.success?
    for candidate in c.stdout.split("\n")
303
      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
304
305
306
307
308
309
310
      if luks_info.success? and luks_info.stdout.match("^\s+device:\s+#{dev}$")
        luks_dev = "/dev/mapper/#{candidate}"
        break
      end
    end
  end
  if luks_dev.nil?
311
312
    c = $vm.execute("echo #{@persistence_password} | " +
                    "cryptsetup luksOpen #{dev} #{name}")
313
314
315
    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
    luks_dev = "/dev/mapper/#{name}"
  end
316
317

  # Adapting check_part_integrity() seems like a bad idea so here goes
318
  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
319
320
321
322
  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
  assert info.match("^    IdUsage: +filesystem$")
  assert info.match("^    IdType: +ext[34]$")
  assert info.match("^    IdLabel: +TailsData$")
323
324

  mount_dir = "/mnt/#{name}"
325
  $vm.execute("mkdir -p #{mount_dir}")
326
  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
327
  assert(c.success?,
Tails developers's avatar
Tails developers committed
328
         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
329

330
331
332
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
333
334
end

335
Given /^I enable persistence$/ do
336
337
  @screen.wait('TailsGreeterPersistence.png', 10)
  @screen.type(Sikuli::Key.SPACE)
338
339
  @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
  match = @screen.find('TailsGreeterPersistencePassphrase.png')
340
  @screen.click(match.getCenter.offset(match.w*2, match.h/2))
341
  @screen.type(@persistence_password)
342
343
end

344
345
def tails_persistence_enabled?
  persistence_state_file = "/var/lib/live/config/tails.persistence"
346
347
  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
         $vm.execute(". '#{persistence_state_file}' && " +
348
                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
349
350
end

351
352
Given /^all persistence presets(| from the old Tails version)(| but the first one) are enabled$/ do |old_tails, except_first|
  assert(old_tails.empty? || except_first.empty?, "Unsupported case.")
353
354
355
  try_for(120, :msg => "Persistence is disabled") do
    tails_persistence_enabled?
  end
356
  unexpected_mounts = Array.new
357
  # Check that all persistent directories are mounted
358
359
  if old_tails.empty?
    expected_mounts = persistent_mounts
360
361
362
363
364
365
    if ! except_first.empty?
      first_expected_mount_source      = expected_mounts.keys[0]
      first_expected_mount_destination = expected_mounts[first_expected_mount_source]
      expected_mounts.delete(first_expected_mount_source)
      unexpected_mounts = [first_expected_mount_destination]
    end
366
  else
367
    assert_not_nil($remembered_persistence_mounts)
368
    expected_mounts = $remembered_persistence_mounts
369
  end
370
  mount = $vm.execute("mount").stdout.chomp
371
  for _, dir in expected_mounts do
372
373
374
    assert(mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is not mounted")
  end
375
376
377
378
  for dir in unexpected_mounts do
    assert(! mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is mounted")
  end
379
380
end

381
382
383
384
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

385
386
Given /^I enable read-only persistence$/ do
  step "I enable persistence"
387
  @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
388
389
end

390
def boot_device
391
392
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
393
394
  boot_dev_id = $vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
  boot_dev = $vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
395
396
397
  return boot_dev
end

anonym's avatar
anonym committed
398
def device_info(dev)
399
400
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
401
402
403
404
405
406
  info = $vm.execute("udevadm info --query=property --name='#{dev}'").stdout.chomp
  info.split("\n").map { |e| e.split('=') } .to_h
end

def boot_device_type
  device_info(boot_device)['ID_BUS']
407
408
end

409
410
411
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
  bus = bus.downcase
  case bus
412
  when "sata"
413
414
415
416
417
    expected_bus = "ata"
  else
    expected_bus = bus
  end
  assert_equal(expected_bus, boot_device_type)
418
  actual_dev = boot_device
419
420
421
422
423
424
  # The boot partition differs between an using Tails installer and
  # isohybrids. There's also a strange case isohybrids are thought to
  # be booting from the "raw" device, and not a partition of it
  # (#10504).
  expected_devs = ['', '1', '4'].map { |e| $vm.disk_dev(name) + e }
  assert(expected_devs.include?(actual_dev),
425
         "We are running from device #{actual_dev}, but for #{bus} drive " +
426
         "'#{name}' we expected to run from one of #{expected_devs}")
427
428
429
430
431
end

Then /^the boot device has safe access rights$/ do

  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
432
  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
433
  assert(devs.size > 0, "Could not determine boot device")
434
  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
435
  all_users_with_groups = all_users.collect do |user|
436
    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
437
438
439
    [user, groups]
  end
  for dev in devs do
440
441
442
    dev_owner = $vm.execute("stat -c %U #{dev}").stdout.chomp
    dev_group = $vm.execute("stat -c %G #{dev}").stdout.chomp
    dev_perms = $vm.execute("stat -c %a #{dev}").stdout.chomp
443
    assert_equal("root", dev_owner)
444
445
    assert(dev_group == "disk" || dev_group == "root",
           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
446
           "'disk' or 'root'.")
447
    assert_equal("660", dev_perms)
448
449
450
451
452
453
454
    for user, groups in all_users_with_groups do
      next if user == "root"
      assert(!(groups.include?(dev_group)),
             "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
             "owns boot device '#{dev}'")
    end
  end
455

456
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
457
  assert(info.match("^    HintSystem: +true$"),
458
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
459
460
end

461
Then /^all persistent filesystems have safe access rights$/ do
462
  persistent_volumes_mountpoints.each do |mountpoint|
463
464
465
    fs_owner = $vm.execute("stat -c %U #{mountpoint}").stdout.chomp
    fs_group = $vm.execute("stat -c %G #{mountpoint}").stdout.chomp
    fs_perms = $vm.execute("stat -c %a #{mountpoint}").stdout.chomp
466
467
468
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
469
470
471
  end
end

472
Then /^all persistence configuration files have safe access rights$/ do
473
  persistent_volumes_mountpoints.each do |mountpoint|
474
    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
475
           "#{mountpoint}/persistence.conf does not exist, while it should")
476
    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
477
           "#{mountpoint}/live-persistence.conf does exist, while it should not")
478
    $vm.execute(
479
480
      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
    ).stdout.chomp.split.each do |f|
481
482
483
      file_owner = $vm.execute("stat -c %U '#{f}'").stdout.chomp
      file_group = $vm.execute("stat -c %G '#{f}'").stdout.chomp
      file_perms = $vm.execute("stat -c %a '#{f}'").stdout.chomp
484
485
486
      assert_equal("tails-persistence-setup", file_owner)
      assert_equal("tails-persistence-setup", file_group)
      assert_equal("600", file_perms)
487
    end
Tails developers's avatar
Tails developers committed
488
  end
489
490
end

491
Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
492
493
494
  if old_tails.empty?
    expected_dirs = persistent_dirs
  else
495
    assert_not_nil($remembered_persistence_dirs)
496
    expected_dirs = $remembered_persistence_dirs
497
  end
498
  persistent_volumes_mountpoints.each do |mountpoint|
499
    expected_dirs.each do |src, dest|
Tails developers's avatar
Tails developers committed
500
      full_src = "#{mountpoint}/#{src}"
501
502
503
      assert_vmcommand_success $vm.execute("test -d #{full_src}")
      dir_perms = $vm.execute_successfully("stat -c %a '#{full_src}'").stdout.chomp
      dir_owner = $vm.execute_successfully("stat -c %U '#{full_src}'").stdout.chomp
504
      if dest.start_with?("/home/#{LIVE_USER}")
505
        expected_perms = "700"
506
        expected_owner = LIVE_USER
507
508
509
510
511
512
513
514
515
516
      else
        expected_perms = "755"
        expected_owner = "root"
      end
      assert_equal(expected_perms, dir_perms,
                   "Persistent source #{full_src} has permission " \
                   "#{dir_perms}, expected #{expected_perms}")
      assert_equal(expected_owner, dir_owner,
                   "Persistent source #{full_src} has owner " \
                   "#{dir_owner}, expected #{expected_owner}")
517
518
519
520
    end
  end
end

521
When /^I write some files expected to persist$/ do
522
  persistent_mounts.each do |_, dir|
523
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
524
    assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?,
525
           "Could not create file in persistent directory #{dir}")
526
527
528
  end
end

529
530
531
532
533
534
When /^I write some dotfile expected to persist$/ do
  assert($vm.execute("touch /live/persistence/TailsData_unlocked/dotfiles/.XXX_persist",
                     :user => LIVE_USER).success?,
         "Could not create a file in the dotfiles persistence.")
end

535
When /^I remove some files expected to persist$/ do
536
  persistent_mounts.each do |_, dir|
537
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
538
    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
539
           "Could not remove file in persistent directory #{dir}")
540
541
542
543
  end
end

When /^I write some files not expected to persist$/ do
544
  persistent_mounts.each do |_, dir|
545
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
546
    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
547
           "Could not create file in persistent directory #{dir}")
548
549
550
  end
end

551
When /^I take note of which persistence presets are available$/ do
552
553
  $remembered_persistence_mounts = persistent_mounts
  $remembered_persistence_dirs = persistent_dirs
554
555
556
557
558
559
end

Then /^the expected persistent files(| created with the old Tails version) are present in the filesystem$/ do |old_tails|
  if old_tails.empty?
    expected_mounts = persistent_mounts
  else
560
    assert_not_nil($remembered_persistence_mounts)
561
    expected_mounts = $remembered_persistence_mounts
562
563
  end
  expected_mounts.each do |_, dir|
564
    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
565
           "Could not find expected file in persistent directory #{dir}")
566
    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
567
568
569
570
           "Found file that should not have persisted in persistent directory #{dir}")
  end
end

571
572
573
574
575
576
577
578
Then /^the expected persistent dotfile is present in the filesystem$/ do
  expected_dirs = persistent_dirs
  assert($vm.execute("test -L #{expected_dirs['dotfiles']}/.XXX_persist").success?,
         "Could not find expected persistent dotfile link.")
  assert($vm.execute("test -e $(readlink -f #{expected_dirs['dotfiles']}/.XXX_persist)").success?,
           "Could not find expected persistent dotfile link target.")
end

579
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
580
  assert(!$vm.is_running?)
581
  disk = {
582
    :path => $vm.storage.disk_path(name),
583
    :opts => {
584
      :format => $vm.storage.disk_format(name),
585
586
587
      :readonly => true
    }
  }
588
  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
589
590
591
592
593
594
595
596
597
    partitions = g.part_list(disk_handle).map do |part_desc|
      disk_handle + part_desc["part_num"].to_s
    end
    partition = partitions.find do |part|
      g.blkid(part)["PART_ENTRY_NAME"] == "TailsData"
    end
    assert_not_nil(partition, "Could not find the 'TailsData' partition " \
                              "on disk '#{disk_handle}'")
    luks_mapping = File.basename(partition) + "_unlocked"
598
    g.luks_open(partition, @persistence_password, luks_mapping)
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
    luks_dev = "/dev/mapper/#{luks_mapping}"
    mount_point = "/"
    g.mount(luks_dev, mount_point)
    assert_not_nil($remembered_persistence_mounts)
    $remembered_persistence_mounts.each do |dir, _|
      # Guestfs::exists may have a bug; if the file exists, 1 is
      # returned, but if it doesn't exist false is returned. It seems
      # the translation of C types into Ruby types is glitchy.
      assert(g.exists("/#{dir}/XXX_persist") == 1,
             "Could not find expected file in persistent directory #{dir}")
      assert(g.exists("/#{dir}/XXX_gone") != 1,
             "Found file that should not have persisted in persistent directory #{dir}")
    end
    g.umount(mount_point)
    g.luks_close(luks_dev)
  end
615
end
616
617

When /^I delete the persistent partition$/ do
618
  step 'I start "Delete persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
619
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
620
621
622
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
623
624

Then /^Tails has started in UEFI mode$/ do
625
  assert($vm.execute("test -d /sys/firmware/efi").success?,
626
627
         "/sys/firmware/efi does not exist")
 end
628
629

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
630
  $vm.storage.disk_mklabel(name, type)
631
end
632

633
Then /^a suitable USB device is (?:still )?not found$/ do
634
  @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
635
636
end

637
Then /^the "(?:[^"]+)" USB drive is selected$/ do
638
639
640
641
642
643
  @screen.wait("TailsInstallerQEMUHardDisk.png", 30)
end

Then /^no USB drive is selected$/ do
  @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
end
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721

Given /^the file system changes introduced in version (.+) are (not )?present(?: in the (\S+) Browser's chroot)?$/ do |version, not_present, chroot_browser|
  assert_equal('1.1~test', version)
  upgrade_applied = not_present.nil?
  chroot_browser = "#{chroot_browser.downcase}-browser" if chroot_browser
  changes = [
    {
      filesystem: :rootfs,
      path: 'some_new_file',
      status: :added,
      new_content: <<-EOF
Some content
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'etc/amnesia/version',
      status: :modified,
      new_content: <<-EOF
#{version} - 20380119
ffffffffffffffffffffffffffffffffffffffff
live-build: 3.0.5+really+is+2.0.12-0.tails2
live-boot: 4.0.2-1
live-config: 4.0.4-1
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'etc/os-release',
      status: :modified,
      new_content: <<-EOF
TAILS_PRODUCT_NAME="Tails"
TAILS_VERSION_ID="#{version}"
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'usr/share/common-licenses/BSD',
      status: :removed
    },
    {
      filesystem: :medium,
      path: 'utils/linux/syslinux',
      status: :removed
    },
  ]
  changes.each do |change|
    case change[:filesystem]
    when :rootfs
      path = '/'
      path += "var/lib/#{chroot_browser}/chroot/" if chroot_browser
      path += change[:path]
    when :medium
      path = '/lib/live/mount/medium/' + change[:path]
    else
      raise "Unknown filesysten '#{change[:filesystem]}'"
    end
    case change[:status]
    when :removed
      assert_equal(!upgrade_applied, $vm.file_exist?(path))
    when :added
      assert_equal(upgrade_applied, $vm.file_exist?(path))
      if upgrade_applied && change[:new_content]
        assert_equal(change[:new_content], $vm.file_content(path))
      end
    when :modified
      assert($vm.file_exist?(path))
      if upgrade_applied
        assert_not_nil(change[:new_content])
        assert_equal(change[:new_content], $vm.file_content(path))
      end
    else
      raise "Unknown status '#{change[:status]}'"
    end
  end
end

Then /^I am proposed to install an incremental upgrade to version (.+)$/ do |version|
722
  recovery_proc = Proc.new do
723
    recover_from_upgrader_failure
724
725
726
727
728
729
730
  end
  failure_pic = 'TailsUpgraderFailure.png'
  success_pic = "TailsUpgraderUpgradeTo#{version}.png"
  retry_tor(recovery_proc) do
    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
    assert_equal(success_pic, match)
  end
731
732
733
734
735
736
end

When /^I agree to install the incremental upgrade$/ do
  @screen.click('TailsUpgraderUpgradeNowButton.png')
end

anonym's avatar
anonym committed
737
738
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
739
740
741
742
743
744
745
746
747
748
749
  recovery_proc = Proc.new do
    recover_from_upgrader_failure
    step "I am proposed to install an incremental upgrade to version #{version}"
    step 'I agree to install the incremental upgrade'
  end
  failure_pic = 'TailsUpgraderFailure.png'
  success_pic = "TailsUpgraderDone.png"
  retry_tor(recovery_proc) do
    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
    assert_equal(success_pic, match)
  end
750
end