usb.rb 28.3 KB
Newer Older
1
2
# Returns a hash that for each persistence preset the running Tails is aware of,
# for each of the corresponding configuration lines,
3
# maps the source to the destination.
4
5
6
def get_persistence_presets_config(skip_links = false)
  # Perl script that prints all persistence configuration lines (one per line)
  # in the form: <mount_point>:<comma-separated-list-of-options>
7
8
9
10
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
11
12
  foreach my $atom (Tails::Persistence::Configuration::Presets->new()->atoms) {
    say $atom->destination, ":", join(",", @{$atom->options});
13
  }
14
15
16
17
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
18
19
20
21
22
  presets_configs = $vm.execute_successfully("perl -E '#{script}'")
                       .stdout.chomp.split("\n")
  assert presets_configs.size >= 10,
         "Got #{presets_configs.size} persistence preset configuration lines, " +
         "which is too few"
23
  persistence_mapping = Hash.new
24
  for line in presets_configs
25
26
27
28
29
    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 }
30
31
32
33
34
35
36
    # 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
37
38
39
40
41
42
    persistence_mapping[source] = destination
  end
  return persistence_mapping
end

def persistent_dirs
43
  get_persistence_presets_config
44
45
46
end

def persistent_mounts
47
  get_persistence_presets_config(true)
48
49
end

50
def persistent_volumes_mountpoints
51
  $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
52
53
end

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# Returns an array that for each persistence preset the running Tails is aware of,
# contains a hash with the following keys: id, enabled, has_configuration_button.
def persistent_presets_ui_settings
  # Perl script that prints all persistence presets
  # in the form: <id>:<enabled>:<has_configuration_button>
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
  foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
    say(sprintf(
      "%s:%s:%s",
      $preset->{id},
      ($preset->{enabled} ? 1 : 0),
      (exists($preset->{configuration_cb}) && defined($preset->{configuration_cb})
         ? 1
         : 0
      ),
    ));
  }
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
  presets = $vm.execute_successfully("perl -E '#{script}'")
               .stdout.chomp.split("\n")
  assert presets.size >= 10,
         "Got #{presets.size} persistence presets, " +
         "which is too few"
  presets_ui_settings = Array.new
  for line in presets
    id, enabled, has_configuration_button = line.split(":")
    presets_ui_settings += [{
      'id'                       => id,
      'enabled'                  => (enabled == '1'),
      'has_configuration_button' => (has_configuration_button == '1'),
    }]
  end
  return presets_ui_settings
end


96
97
98
99
100
101
102
103
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

104
Given /^I clone USB drive "([^"]+)" to a (new|temporary) USB drive "([^"]+)"$/ do |from, mode, to|
105
  $vm.storage.clone_to_new_disk(from, to)
106
107
108
  if mode == 'temporary'
    add_after_scenario_hook { $vm.storage.delete_volume(to) }
  end
109
110
111
end

Given /^I unplug USB drive "([^"]+)"$/ do |name|
112
  $vm.unplug_drive(name)
113
114
end

115
Given /^the computer is set to boot from the old Tails DVD$/ do
116
  $vm.set_cdrom_boot(OLD_TAILS_ISO)
117
118
end

119
Given /^the computer is set to boot in UEFI mode$/ do
120
  $vm.set_os_loader('UEFI')
121
122
123
  @os_loader = 'UEFI'
end

anonym's avatar
anonym committed
124
def tails_installer_selected_device
125
  @installer.child('Target USB stick:', roleName: 'label').parent
anonym's avatar
anonym committed
126
127
128
129
130
    .child('', roleName: 'combo box', recursive: false).name
end

def tails_installer_is_device_selected?(name)
  device = $vm.disk_dev(name)
131
  tails_installer_selected_device[/\(#{device}\d*\)$/]
anonym's avatar
anonym committed
132
133
end

anonym's avatar
anonym committed
134
def tails_installer_match_status(pattern)
anonym's avatar
anonym committed
135
  @installer.child('', roleName: 'text').text[pattern]
anonym's avatar
anonym committed
136
137
end

138
When /^I start Tails Installer$/ do
anonym's avatar
anonym committed
139
  @installer_log_path = '/tmp/tails-installer.log'
140
  step "I run \"/usr/bin/tails-installer --verbose > #{@installer_log_path} 2>&1\" in GNOME Terminal"
141
  @installer = Dogtail::Application.new('tails-installer')
142
  @installer.child('Tails Installer', roleName: 'frame')
143
144
145
146
147
  # Sometimes Dogtail will find the Installer and click its window
  # before it is shown (searchShowingOnly is not perfect) which
  # generally means clicking somewhere on the Terminal => the click is
  # lost *and* the installer does not go to the foreground. So let's
  # wait a bit extra.
148
149
  sleep 3
  $vm.focus_window('Tails Installer')
150
151
end

152
When /^I am told by Tails Installer that.*"([^"]+)".*$/ do |status|
153
  try_for(10) do
154
    tails_installer_match_status(status)
155
156
157
  end
end

158
Then /^a suitable USB device is (?:still )?not found$/ do
159
160
161
  @installer.child(
    'No device suitable to install Tails could be found', roleName: 'label'
  )
162
163
164
165
166
167
168
169
170
171
172
173
end

Then /^(no|the "([^"]+)") USB drive is selected$/ do |mode, name|
  try_for(30) do
    if mode == 'no'
      tails_installer_selected_device == ''
    else
      tails_installer_is_device_selected?(name)
    end
  end
end

174
When /^I (install|reinstall|upgrade) Tails (?:to|on) USB drive "([^"]+)" (by cloning|from an ISO)$/ do |action, name, source|
175
  step "I start Tails Installer"
176
177
178
  # If the device was plugged *just* before this step, it might not be
  # completely ready (so it's shown) at this stage.
  try_for(10) { tails_installer_is_device_selected?(name) }
179
180
181
182
183
184
185
186
187
188
189
190
  if source == 'from an ISO'
    iso_radio = @installer.child('Use a downloaded Tails ISO image',
                                 roleName: 'radio button')
    iso_radio.click
    iso_radio.parent.button('(None)').click
    file_chooser = @installer.child('Select a File', roleName: 'file chooser')
    @screen.type("l", Sikuli::KeyModifier.CTRL)
    # The only visible text element will be the path entry
    file_chooser.child(roleName: 'text').typeText(@iso_path + '\n')
    file_chooser.button('Open').click
  end
  begin
191
192
193
194
195
196
    if action == 'reinstall'
      label = 'Reinstall (delete all data)'
    else
      label = action.capitalize
    end
    @installer.button(label).click
197
    @installer.child('Question', roleName: 'alert').button('Yes').click
198
    try_for(15*60, { :delay => 10 }) do
199
200
201
202
203
204
      @installer
        .child('Information', roleName: 'alert')
        .child('Installation complete!', roleName: 'label')
      true
    end
  rescue Exception => e
anonym's avatar
anonym committed
205
206
    debug_log("Tails Installer debug log:\n" +
              $vm.file_content(@installer_log_path))
207
208
    raise e
  end
209
210
end

211
212
213
214
215
Given /^I plug and mount a USB drive containing the Tails ISO$/ do
  iso_dir = share_host_files(TAILS_ISO)
  @iso_path = "#{iso_dir}/#{File.basename(TAILS_ISO)}"
end

216
217
Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
  presets = persistent_presets_ui_settings
  presets[0]['is_first'] = true
  debug_log("presets: #{presets}")
  for setting in presets
    debug_log("on preset: #{setting}")
    tabs_to_select_switch  = 3 # previous switch -> separator -> row -> switch
    tabs_to_select_switch -= 1 if setting['is_first']
    tabs_to_select_switch += 1 if setting['has_configuration_button']
    # Select the switch
    debug_log("typing TAB #{tabs_to_select_switch} times to select the switch")
    tabs_to_select_switch.times do
      debug_log("typing TAB")
      @screen.type(Sikuli::Key.TAB)
    end
    # Activate the switch
    if ! setting['enabled']
      debug_log("pressing space")
      @screen.type(Sikuli::Key.SPACE)
    else
      debug_log("setting already enabled, skipping")
    end
239
  end
240
  @screen.type(Sikuli::Key.ENTER) # Press the Save button
intrigeri's avatar
intrigeri committed
241
  @screen.wait('PersistenceWizardDone.png', 60)
242
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
243
244
end

245
When /^I disable the first persistence preset$/ do
anonym's avatar
anonym committed
246
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
247
  @screen.wait('PersistenceWizardPresets.png', 300)
248
  @screen.type(Sikuli::Key.TAB)
249
  @screen.type(Sikuli::Key.SPACE)
250
  @screen.type(Sikuli::Key.ENTER)
251
252
253
254
  @screen.wait('PersistenceWizardDone.png', 30)
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
end

255
Given /^I create a persistent partition$/ do
anonym's avatar
anonym committed
256
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
257
  @screen.wait('PersistenceWizardStart.png', 60)
258
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
259
  @screen.wait('PersistenceWizardPresets.png', 300)
260
261
262
  step "I enable all persistence presets"
end

263
def check_disk_integrity(name, dev, scheme)
264
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
265
266
267
268
269
270
271
  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

272
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
273
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
274
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
275
276
  dev_info = info_split[0]
  part_info = info_split[1]
277
  assert(dev_info.match("^    IdUsage: +#{usage}$"),
278
         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
279
280
281
  assert(dev_info.match("^    IdType: +#{fs_type}$"),
         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
  assert(part_info.match("^    Name: +#{part_label}$"),
282
         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
283
284
285
286
  if part_type
    assert(part_info.match("^    Type: +#{part_type}$"),
           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
  end
287
288
end

289
def tails_is_installed_helper(name, tails_root, loader)
290
  disk_dev = $vm.disk_dev(name)
291
292
  part_dev = disk_dev + "1"
  check_disk_integrity(name, disk_dev, "gpt")
293
294
295
  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
                       # EFI System Partition
                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
296

297
  target_root = "/mnt/new"
298
  $vm.execute("mkdir -p #{target_root}")
299
  $vm.execute("mount #{part_dev} #{target_root}")
300

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

305
  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
306
  # We deal with these files separately
307
  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
308
  for f in syslinux_files - ignores do
309
    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
310
                    "'#{target_root}/syslinux/#{f}'")
311
312
313
314
315
    assert(c.success?, "USB drive '#{name}' has differences in " +
           "'/syslinux/#{f}'")
  end

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

321
322
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
323
324
end

325
326
327
328
329
330
331
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|
  iso_root = "/mnt/iso"
332
  $vm.execute("mkdir -p #{iso_root}")
333
  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
Tails developers's avatar
Tails developers committed
334
  tails_is_installed_helper(target_name, iso_root, "isolinux")
335
  $vm.execute("umount #{iso_root}")
336
337
end

338
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
339
340
  data_part_dev = $vm.disk_dev(name) + "2"
  assert(!$vm.execute("test -b #{data_part_dev}").success?,
341
342
343
         "USB drive #{name} has a partition '#{data_part_dev}'")
end

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

348
349
  # The LUKS container may already be opened, e.g. by udisks after
  # we've run tails-persistence-setup.
350
  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
351
352
  if c.success?
    for candidate in c.stdout.split("\n")
353
      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
354
355
356
357
358
359
360
      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?
361
362
    c = $vm.execute("echo #{@persistence_password} | " +
                    "cryptsetup luksOpen #{dev} #{name}")
363
364
365
    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
    luks_dev = "/dev/mapper/#{name}"
  end
366
367

  # Adapting check_part_integrity() seems like a bad idea so here goes
368
  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
369
370
371
372
  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
  assert info.match("^    IdUsage: +filesystem$")
  assert info.match("^    IdType: +ext[34]$")
  assert info.match("^    IdLabel: +TailsData$")
373
374

  mount_dir = "/mnt/#{name}"
375
  $vm.execute("mkdir -p #{mount_dir}")
376
  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
377
  assert(c.success?,
Tails developers's avatar
Tails developers committed
378
         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
379

380
381
382
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
383
384
end

385
Given /^I enable persistence$/ do
386
387
388
  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 10)
  @screen.type(@persistence_password + Sikuli::Key.ENTER)
  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
389
390
end

391
392
def tails_persistence_enabled?
  persistence_state_file = "/var/lib/live/config/tails.persistence"
393
394
  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
         $vm.execute(". '#{persistence_state_file}' && " +
395
                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
396
397
end

398
399
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.")
400
401
402
  try_for(120, :msg => "Persistence is disabled") do
    tails_persistence_enabled?
  end
403
  unexpected_mounts = Array.new
404
  # Check that all persistent directories are mounted
405
406
  if old_tails.empty?
    expected_mounts = persistent_mounts
407
408
409
410
411
412
    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
413
  else
414
    assert_not_nil($remembered_persistence_mounts)
415
    expected_mounts = $remembered_persistence_mounts
416
  end
417
  mount = $vm.execute("mount").stdout.chomp
418
  for _, dir in expected_mounts do
419
420
421
    assert(mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is not mounted")
  end
422
423
424
425
  for dir in unexpected_mounts do
    assert(! mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is mounted")
  end
426
427
end

428
429
430
431
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

432
def boot_device
433
434
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
435
436
  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
437
438
439
  return boot_dev
end

anonym's avatar
anonym committed
440
def device_info(dev)
441
442
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
443
444
445
446
447
448
  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']
449
450
end

451
452
453
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
  bus = bus.downcase
  case bus
454
  when "sata"
455
456
457
458
459
    expected_bus = "ata"
  else
    expected_bus = bus
  end
  assert_equal(expected_bus, boot_device_type)
460
  actual_dev = boot_device
461
462
463
464
465
466
  # 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),
467
         "We are running from device #{actual_dev}, but for #{bus} drive " +
468
         "'#{name}' we expected to run from one of #{expected_devs}")
469
470
471
472
473
end

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

  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
474
  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
475
  assert(devs.size > 0, "Could not determine boot device")
476
  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
477
  all_users_with_groups = all_users.collect do |user|
478
    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
479
480
481
    [user, groups]
  end
  for dev in devs do
482
483
484
    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
485
    assert_equal("root", dev_owner)
486
487
    assert(dev_group == "disk" || dev_group == "root",
           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
488
           "'disk' or 'root'.")
489
    assert_equal("660", dev_perms)
490
491
492
493
494
495
496
    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
497

498
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
499
  assert(info.match("^    HintSystem: +true$"),
500
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
501
502
end

503
Then /^all persistent filesystems have safe access rights$/ do
504
  persistent_volumes_mountpoints.each do |mountpoint|
505
506
507
    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
508
509
510
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
511
512
513
  end
end

514
Then /^all persistence configuration files have safe access rights$/ do
515
  persistent_volumes_mountpoints.each do |mountpoint|
516
    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
517
           "#{mountpoint}/persistence.conf does not exist, while it should")
518
    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
519
           "#{mountpoint}/live-persistence.conf does exist, while it should not")
520
    $vm.execute(
521
522
      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
    ).stdout.chomp.split.each do |f|
523
524
525
      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
526
527
528
      assert_equal("tails-persistence-setup", file_owner)
      assert_equal("tails-persistence-setup", file_group)
      assert_equal("600", file_perms)
529
    end
Tails developers's avatar
Tails developers committed
530
  end
531
532
end

533
Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
534
535
536
  if old_tails.empty?
    expected_dirs = persistent_dirs
  else
537
    assert_not_nil($remembered_persistence_dirs)
538
    expected_dirs = $remembered_persistence_dirs
539
  end
540
  persistent_volumes_mountpoints.each do |mountpoint|
541
    expected_dirs.each do |src, dest|
Tails developers's avatar
Tails developers committed
542
      full_src = "#{mountpoint}/#{src}"
543
544
545
      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
546
      if dest.start_with?("/home/#{LIVE_USER}")
547
        expected_perms = "700"
548
        expected_owner = LIVE_USER
549
550
551
552
553
554
555
556
557
558
      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}")
559
560
561
562
    end
  end
end

563
When /^I write some files expected to persist$/ do
564
  persistent_mounts.each do |_, dir|
565
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
566
    assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?,
567
           "Could not create file in persistent directory #{dir}")
568
569
570
  end
end

571
572
573
574
575
576
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

577
When /^I remove some files expected to persist$/ do
578
  persistent_mounts.each do |_, dir|
579
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
580
    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
581
           "Could not remove file in persistent directory #{dir}")
582
583
584
585
  end
end

When /^I write some files not expected to persist$/ do
586
  persistent_mounts.each do |_, dir|
587
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
588
    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
589
           "Could not create file in persistent directory #{dir}")
590
591
592
  end
end

593
When /^I take note of which persistence presets are available$/ do
594
595
  $remembered_persistence_mounts = persistent_mounts
  $remembered_persistence_dirs = persistent_dirs
596
597
598
599
600
601
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
602
    assert_not_nil($remembered_persistence_mounts)
603
    expected_mounts = $remembered_persistence_mounts
604
605
  end
  expected_mounts.each do |_, dir|
606
    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
607
           "Could not find expected file in persistent directory #{dir}")
608
    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
609
610
611
612
           "Found file that should not have persisted in persistent directory #{dir}")
  end
end

613
614
615
616
617
618
619
620
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

621
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
622
  assert(!$vm.is_running?)
623
  disk = {
624
    :path => $vm.storage.disk_path(name),
625
    :opts => {
626
      :format => $vm.storage.disk_format(name),
627
628
629
      :readonly => true
    }
  }
630
  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
631
632
633
634
635
636
637
638
639
    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"
640
    g.luks_open(partition, @persistence_password, luks_mapping)
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
    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
657
end
658
659

When /^I delete the persistent partition$/ do
anonym's avatar
anonym committed
660
  step 'I start "Delete persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
661
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
662
663
664
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
665
666

Then /^Tails has started in UEFI mode$/ do
667
  assert($vm.execute("test -d /sys/firmware/efi").success?,
668
669
         "/sys/firmware/efi does not exist")
 end
670
671

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
672
  $vm.storage.disk_mklabel(name, type)
673
end
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
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
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|
752
  recovery_proc = Proc.new do
753
    recover_from_upgrader_failure
754
755
756
757
758
759
760
  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
761
762
763
764
765
766
end

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

anonym's avatar
anonym committed
767
768
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
769
770
771
772
773
774
775
776
777
778
779
  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
780
end