usb.rb 28.5 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
# 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),
68
      (exists($preset->{configuration_app_desktop_id}) && defined($preset->{configuration_app_desktop_id})
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
         ? 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
def recover_from_upgrader_failure
intrigeri's avatar
intrigeri committed
97
98
99
100
101
  $vm.execute('pkill --full tails-upgrade-frontend-wrapper')
  $vm.execute('killall tails-upgrade-frontend zenity')
  # Do not sleep when retrying
  $vm.execute_successfully('/usr/local/bin/tails-upgrade-frontend-wrapper --no-wait')
  $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
102
103
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
198
199
200
201
    if action == 'install' || action == 'reinstall'
      @installer.child('Question', roleName: 'alert').button('Install').click
    else
      @installer.child('Question', roleName: 'alert').button('Upgrade').click
    end
202
    try_for(15*60, { :delay => 10 }) do
203
204
205
206
207
208
      @installer
        .child('Information', roleName: 'alert')
        .child('Installation complete!', roleName: 'label')
      true
    end
  rescue Exception => e
anonym's avatar
anonym committed
209
210
    debug_log("Tails Installer debug log:\n" +
              $vm.file_content(@installer_log_path))
211
212
    raise e
  end
213
214
end

215
216
217
218
219
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

220
221
Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  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
243
  end
244
  @screen.type(Sikuli::Key.ENTER) # Press the Save button
intrigeri's avatar
intrigeri committed
245
  @screen.wait('PersistenceWizardDone.png', 60)
246
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
247
248
end

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

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

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

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

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

301
  target_root = "/mnt/new"
302
  $vm.execute("mkdir -p #{target_root}")
303
  $vm.execute("mount #{part_dev} #{target_root}")
304

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

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

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

325
326
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
327
328
end

329
330
331
332
333
334
335
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"
336
  $vm.execute("mkdir -p #{iso_root}")
337
  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
Tails developers's avatar
Tails developers committed
338
  tails_is_installed_helper(target_name, iso_root, "isolinux")
339
  $vm.execute("umount #{iso_root}")
340
341
end

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

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

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

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

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

384
385
386
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
387
388
end

389
Given /^I enable persistence$/ do
intrigeri's avatar
intrigeri committed
390
  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 60)
391
392
  @screen.type(@persistence_password + Sikuli::Key.ENTER)
  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
393
394
end

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

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

432
433
434
435
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

436
def boot_device
437
438
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
439
440
  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
441
442
443
  return boot_dev
end

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

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

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

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

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

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

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

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

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

577
578
579
580
581
582
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

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

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

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

619
620
621
622
623
624
625
626
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

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

When /^I delete the persistent partition$/ do
anonym's avatar
anonym committed
666
  step 'I start "Delete persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
667
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
668
669
670
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
671
672

Then /^Tails has started in UEFI mode$/ do
673
  assert($vm.execute("test -d /sys/firmware/efi").success?,
674
675
         "/sys/firmware/efi does not exist")
 end
676
677

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
678
  $vm.storage.disk_mklabel(name, type)
679
end
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
752
753
754
755
756
757
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|
758
  recovery_proc = Proc.new do
759
    recover_from_upgrader_failure
760
761
762
763
764
765
766
  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
767
768
769
770
771
772
end

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

anonym's avatar
anonym committed
773
774
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
775
776
777
778
779
780
781
782
783
784
785
  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
786
end