Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release SecureDrop 2.1.0 #6103

Closed
25 of 26 tasks
eloquence opened this issue Sep 21, 2021 · 17 comments · Fixed by #6151
Closed
25 of 26 tasks

Release SecureDrop 2.1.0 #6103

eloquence opened this issue Sep 21, 2021 · 17 comments · Fixed by #6151
Labels
epic Meta issue tracking child issues

Comments

@eloquence
Copy link
Member

eloquence commented Sep 21, 2021

This is a tracking issue for the release of SecureDrop 2.1.0

Scheduled as follows:

Feature / string freeze: 2021-09-28
Pre-release announcement: 2021-10-12
Release date: 2021-10-19
Release manager: @zenmonkeykstop
Deputy release manager: @conorsch
Communications manager: @eloquence
Localization manager: @conorsch
Deputy LM: @cfm [tentative]

QA team: @creviera @tesitura @cfm @conorsch @zenmonkeykstop

SecureDrop maintainers and testers: As you QA 2.1.0, please report back your testing results as comments on this ticket. File GitHub issues for any problems found, tag them "QA: Release", and associate them with the 2.1.0 milestone for tracking (or ask a maintainer to do so).

Test debian packages will be posted on https://apt-test.freedom.press signed with the test key

QA Matrix for 2.1.0

Test Plan for 2.1.0

Prepare release candidate (2.1.0~rc1)

  • Link to latest version of Tails, including release candidates, to test against during QA
  • Prepare 2.1.0~rc1 release changelog
  • Branch off release/2.1.0 from release/2.0.2
  • Prepare 2.1.0~rc1
  • Build debs, preserving build log, and put up 2.1.0~rc1 on test apt server
  • Commit build log.

Prepare release candidate (2.1.0~rc2)

  • Link to latest version of Tails, including release candidates, to test against during QA
  • Prepare 2.1.0~rc2, with changelog
  • Build debs, preserving build log, and put up 2.1.0~rc2 on test apt server - Add SecureDrop 2.1.0~rc2 debs securedrop-apt-test#125
  • Commit build log.

After each test, please update the QA matrix and post details for Basic Server Testing, Application Acceptance Testing and release-specific testing below in comments to this ticket.

Final release

  • Ensure builder in release branch is updated and/or update builder image
  • Push signed tag
  • Pre-Flight: Test updater logic in Tails (apt-qa tracks the release branch in the LFS repo)
  • Build final Debian packages for 2.1.0 (and preserve build log)
  • Commit package build log to https://github.com/freedomofpress/build-logs
  • Upload Debian packages to apt-qa server
  • Pre-Flight: Test that install and upgrade from 2.0.2 to 2.1.0 works w/ prod repo debs (apt-qa.freedom.press polls the release branch in the LFS repo for the debs)
  • Flip apt QA server to prod status (merge to main in the LFS repo)
  • Merge Docs branch changes to main and verify new docs build in securedrop-docs repo
  • Prepare release messaging

Post release

@eloquence
Copy link
Member Author

eloquence commented Oct 5, 2021

Draft release comms are ready for initial review. As always, it's a bit of a judgment call which changes should go into the blog post and which ones are only in the changelog -- if there's stuff you feel warrants more or less visibility, please don't hesitate to comment.

@cfm
Copy link
Member

cfm commented Oct 6, 2021

NB. An upgrade scenario newly provisioned from a tag (i.e., without #6120) will fail in securedrop-admin install role install-fpf-repo with apt cache update failed until both VMs are brought up to date via (e.g.):

root@sd-staging:~/securedrop# molecule login -s libvirt-prod-focal -h app-prod
vagrant@app-prod:~$ sudo apt-get update
vagrant@app-prod:~$ sudo apt-get upgrade -y
vagrant@app-prod:~$ exit
root@sd-staging:~/securedrop# molecule login -s libvirt-prod-focal -h mon-prod
vagrant@mon-prod:~$ sudo apt-get update
vagrant@mon-prod:~$ sudo apt-get upgrade -y
vagrant@mon-prod:~$ exit

@zenmonkeykstop
Copy link
Contributor

Yup, that's the same root cause as captured in #6119. If you have more up-to-date bento/20.04 boxes it shouldn't be a problem, but it doesn't look like those are available yet in a provider format that we can use.

@cfm
Copy link
Member

cfm commented Oct 8, 2021

Summary

Test plan and results

  • If you are testing the upgrade scenario, you should create source and journalist accounts before performing the upgrade - some test cases require existing accounts.

  • In order to test Add support for support TLSv1.3 ciphersuites as for #4769 #5988 you will need to enable HTTPS on the source interface. To do so using self-signed certs:

    • Use the command cd ~/Persistent/securedrop && make self-signed-https-certs on the Admin Workstation to generate the necessary files
    • Choose the appropriate options while running ./securedrop-admin sdconfig
    • Accept the self-signed cert when prompted with the HTTPS warning in Tor Browser.

Environment

  • Install target: VMs
  • Tails version: 4.22
  • Test Scenario: Upgrade (HTTPS enabled)
  • SSH over Tor: yes
  • Release candidate: 2.1.0~rc1
  • General notes:
  1. Take a backup of your existing SecureDrop installation using the securedrop-admin backup command. You will need enough free space on your Admin Workstation USB to complete this backup.

  2. Either run the ansible QA playbook from qa-update-playbook (pending updated QA repo playbook for Focal #6123):

    # Steps to use this playbook from an Admin Workstation:
    #
    # 1. Check out the current production release tag.
    # 2. Provision a SecureDrop instance (hardware or VMs).
    # 4. Run `./securedrop-admin tailsconfig`
    # 5. Run `source admin/.venv3/bin/activate` (so ansible commands work)
    # 6. Run `cd install_files/ansible-base`
    # 7. Run `ansible-playbook -vv --diff securedrop-qa.yml`
    # 8. `ssh app` # start interactive session
    # 9. On the Application Server, run `sudo unattended-upgrades -d`
    # 10. Reboot the server once the upgrade is complete
    # 11. Repeat steps 8-10 on the Monitor server

[...]

  1. Complete steps 6 and 7 of clean install QA instructions (kernel testing, QA Matrix and Github release ticket testing).

Basic Server Testing

  • After installing the testinfra dependencies, all tests in ./securedrop-admin verify are passing:
    • Install dependencies on Admin Workstation with cd ~/Persistent/securedrop && ./securedrop-admin setup -t
    • Run tests with ./securedrop-admin verify (this will take a while)
    • Remove test dependencies: rm -rf admin/.venv3/ && ./securedrop-admin setup

Tests failing: (see also: #6127)

  • test_hosts_files: expects original configured hostname mon (≠ mon-prod)

  • test_fpf_apt_repo_present: expects apt.freedom.press rather than apt-test.freedom.press used for QA

  • test_unattended_upgrades_functional

  • test_postfix_generic_maps: expects original configured hostname mon (≠ mon-prod)

  • QA Matrix checks pass

Command Line User Generation

  • Can successfully add admin user and login:

Administration

  • I have backed up and successfully restored the app server following the backup documentation
  • If doing upgrade testing, make a backup on 2.0.2 and restore this backup on this release candidate
  • "Send Test OSSEC Alert" button in the journalist triggers an OSSEC alert and an email is sent
  • [-] Can successfully add journalist account with HOTP authentication: don't have spare Yubikey to test with

Application Acceptance Testing

Source Interface

Landing page base cases
  • JS warning bar does not appear when using Security Slider high
  • JS warning bar does appear when using Security Slider Low
First submission base cases
  • On generate page, refreshing codename produces a new 7-word codename
  • On submit page, empty submissions produce flashed message
  • On submit page, short message submitted successfully
  • On submit page, file greater than 500 MB produces "The connection was reset" in Tor Browser quickly before the entire file is uploaded
  • On submit page, file less than 500 MB submitted successfully
Returning source base cases
  • Nonexistent codename cannot log in
  • Empty codename cannot log in
  • Legitimate codename can log in
  • Returning user can view journalist replies - need to log into journalist interface to test

Journalist Interface

Login base cases
  • Can log in with 2FA tokens
  • incorrect password cannot log in
  • invalid 2fa token cannot log in
  • 2fa immediate reuse cannot log in
  • [-] Journalist account with HOTP can log in: don't have spare Yubikey to test with
Index base cases
  • Filter by codename works
  • Starring and unstarring works
  • Click select all selects all submissions
  • Selecting all and clicking "Download" works
Individual source page
  • You can submit a reply and a flashed message and new row appears
  • You cannot submit an empty reply
  • Clicking "Delete Source Account" and the source and docs are deleted
  • You can click on a document and successfully decrypt using application private key

Basic Tails Testing

After updating to this release candidate and running securedrop-admin tailsconfig

  • The Updater GUI appears on boot
  • Updating to 2.0.2 is successful

2.1.0 release-specific changes

Web Applications

  • #6075 - valid HTML time tags

    • Verify that the SI lookup page HTML validates correctly (e.g. by using a Firefox addon)
  • #5695 - scrypt and sessions refactor

    • Verify that no codename- or session-related errors were observed during application acceptance testing
    • On the Source Interface, create a new user, log out, return to the home page without clearing the Tor Browser identity, and repeat this process at least 5 times. Verify that you do not observe an error wwith the text "you have been logged out due to inactivity" while repeating these steps.
    • If possible, log in to the Source Interface simultaneously as separate users from multiple instances of the Tor Browser, submit messages from each user, and reply to the users from the Journalist Interface.
      • Verify that each user has a distinct codename in the SI and journalist designation in the JI
      • Verify that no errors were observed on the Source Interface in any user session
      • Verify that submitted messages appear on the correct source page on the JI
      • Verify that sources receive the correct reply in the SI and that the reply can be read
      • On the application server, verify that each source has a distinct GPG key fingerprint, e.g. with the command sudo -u www-data gpg --homedir /var/lib/securedrop/keys -k
    • (Upgrade-only) Verify that a source user created before the upgrade can still log in using their existing codename, that their journalist designation is unchanged, and that they can still read existing and new replies
  • #6041 - SI a11y refactor

    • Verify that Source Interface functionality and appearance is unchanged
    • Using the Web Developer tools' Accessibility Inspector and check for issues: All, verify that accessibility issues have been significantly reduced on the SI /lookup page vs the corresponding page on https://demo-source.securedrop.org
    • Compare other pages on the SI in the same way - verify that issues have been removed and no new issues introduced.
  • #5958 - OTP secret length

    • verify that the TOTP secret and corresponding QRcode displayed via manage.py add-admin on the application server are 32 chars long and can be used to generate valid TOTP codes via the Google Authenticator mobile app
    • verify that the TOTP secret and QRCode generated via the JI during user creation are also 32 chars long and valid
    • (upgrade-only) verify that an existing 16-char shared secret can still be used to generate valid TOTP codes and log in
    • (upgrade-only) verify that when a user account with an existing 16-char shared secret has its OTP secret regenerated, the new OTP shared secret is 32 chars long and valid.
    • [-] (HOTP-only) verify that an account using HOTP can still be created and used to log in.: don't have spare Yubikey to test with
  • #5696 - no JS in user delete modal

    • Set the Tor Browser security level to Safest
    • In the Journalist Interface, verify that a confirmation modal dialog is displayed when a user is selected for deletion
    • Choose Cancel and verify that the user was not deleted
    • Select the user for deletion again and choose Delete. Verify that the user was deleted.
  • #5988 - TLS 1.3 only

    • (HTTPS-only) On the Source Interface index page, verify via the Tor Browser Security Tab that the connection is encrypted not only as an Onion Service but also vi TLS 1.3 with 256-bit keys

Tor Browser "Page Info" says (my emphasis):

Connection Encrypted (Onion Service, TLS_AES_128GCM_SHA256, 128 bit keys, TLS 1.3)

After rerunning make self-signed-https-certs && ./securedrop-admin install, Tor Browser "Page Info" says (my emphasis):

Connection Encrypted (Onion Service, TLS_AES_256GCM_SHA384, 256 bit keys, TLS 1.3)

This suggests that an explicit ./securedrop-admin install is necessary for #5988 to go into effect on upgrading an existing SecureDrop installation.

  • (HTTPS-only) On the application server, inspect /etc/apache2/sites-enabled/source.conf and verify that it contains the line SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ ssh app "grep 'SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2' /etc/apache2/sites-enabled/source.conf"
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
  • #5979 - Remove expired key
    • verify that the old signing key with fingerprint 22245C81E3BAEB4138B36061310F561200F4AD77 is not present on the application or monitor servers, e.g. by running the command for s in app mon; do ssh $s sudo apt-key list from the Admin workstation
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ for s in app mon; do ssh $s sudo apt-key list | grep -b2 -a1 "2224"; done
Warning: apt-key output should not be parsed (stdout is not a terminal)
587-pub   rsa4096 2016-10-20 [SC] [expired: 2021-06-30]
639:      2224 5C81 E3BA EB41 38B3  6061 310F 5612 00F4 AD77
696-uid           [ expired] SecureDrop Release Signing Key
Warning: apt-key output should not be parsed (stdout is not a terminal)
587-pub   rsa4096 2016-10-20 [SC] [expired: 2021-06-30]
639:      2224 5C81 E3BA EB41 38B3  6061 310F 5612 00F4 AD77
696-uid           [ expired] SecureDrop Release Signing Key
  • Verify that the new signing key 2359 E653 8C06 13E6 5295 5E6C 188E DD3B 7B22 E6A3 is present on the servers
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ for s in app mon; do ssh $s sudo apt-key list | grep -b2 -a1 "2359"; done
Warning: apt-key output should not be parsed (stdout is not a terminal)
326-pub   rsa4096 2021-05-10 [SC] [expires: 2022-07-04]
378:      2359 E653 8C06 13E6 5295  5E6C 188E DD3B 7B22 E6A3
435-uid           [ unknown] SecureDrop Release Signing Key <[email protected]>
Warning: apt-key output should not be parsed (stdout is not a terminal)
326-pub   rsa4096 2021-05-10 [SC] [expires: 2022-07-04]
378:      2359 E653 8C06 13E6 5295  5E6C 188E DD3B 7B22 E6A3
435-uid           [ unknown] SecureDrop Release Signing Key <[email protected]>
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ ssh mon sudo grep -R "fwupd" /var/ossec/logs/alerts | grep -v "grep" | wc -l
0
  • #5909 - Manually-transferred backup
    • Back up the instance using ./securedrop-admin backup
    • Copy the backup file to the application server using e.g. scp sd-backup.tar.gz app:/tmp/sd-backup-transfer.tar.gz
    • Verify that the command ./securedrop-admin restore --no-transfer sd-backup-transfer.tar.gz completes successfully, and that the local backup file is not transferred to the server during the playbook run.
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ scp install_files/ansible-base/sd-backup-2021-10-08* app:/tmp/sd-backup-2021-10-08.tar.gz
sd-backup-2021-10-08--01-23-08.tar.gz         100%  107KB  72.1KB/s   00:01
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin --force restore --no-transfer sd-backup-2021-10-08.tar.gz
[...]
TASK [restore : Extract Tor configuration from backup] *************************
fatal: [app]: FAILED! => {
    "changed": false
}

MSG:

Source '/home/amnesia/Persistent/securedrop/install_files/ansible-base/sd-backup-2021-10-08.tar.gz' does not exist
[...]
amnesia@amnesia:~/Persistent/securedrop$ scp install_files/ansible-base/sd-backup-2021-10-08* app:/tmp/
sd-backup-2021-10-08--01-23-08.tar.gz         100%  107KB  93.4KB/s   00:01
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin --force restore --no-transfer sd-backup-2021-10-08--01-23-08.tar.gz
[...]
PLAY RECAP *********************************************************************
app                        : ok=17   changed=12   unreachable=0    failed=0    skipped=13   rescued=0    ignored=0

Even with --no-transfer, restore_file needs to exist in install_files/ansible-base—which might be worth documenting further as the intended behavior of #5909.

  • [-] #6110 - Repair Tails installer
    • (Tails version 4.18 or lesser):
      • [-] Verify that ./securedrop-admin tailsconfig completes successfully and the Tails OS Updater starts without displaying errors
      • [-] Verify that Tails can be successfully updated to the latest version.
    • (Tails latest version):
      • [-] Verify that the command ./securedrop-admin tailsconfig completes successfully without triggering the Tails OS Updater.
      • on the admin workstation, edit /etc/os-release to change the TAILS_VERSION_ID to a pre-4.19 version
      • Run touch /usr/local/etc/ssl/certs/tails.boum.org-CA.pem
      • [-] Run `./securedrop-admin tailsconfig and verify that it completes successfully
      • [-] Verify that the contents of this file were added to /usr/local/etc/ssl/certs/tails.boum.org-CA.pem

@conorsch
Copy link
Contributor

conorsch commented Oct 8, 2021

Environment

  • Install target: Prod VMs, via Tails
  • Tails version: 4.23
  • Test Scenario: Clean install w/ HTTPS
  • SSH over Tor: Yes
  • Release candidate: 2.1.0~rc1
  • General notes: I made sure to update the apt packages inside my VMs before getting started

Basic Server Testing

  • After installing the testinfra dependencies, all tests in ./securedrop-admin verify are passing:
    • N.B. the apt repo config tests failed, because I was using apt-test. That's expected.
  • QA Matrix checks pass
    • Yes, with usual caveat of spectre/meltdown checker results not being reliable in VMs

Command Line User Generation

  • Can successfully add admin user and login

Administration

(Not tested)

  • I have backed up and successfully restored the app server following the backup documentation
  • If doing upgrade testing, make a backup on 2.0.2 and restore this backup on this release candidate
  • "Send Test OSSEC Alert" button in the journalist triggers an OSSEC alert and an email is sent
  • Can successfully add journalist account with HOTP authentication

Application Acceptance Testing

Source Interface

Landing page base cases
  • JS warning bar does not appear when using Security Slider high
  • JS warning bar does appear when using Security Slider "Standard"
First submission base cases
  • On generate page, refreshing codename produces a new 7-word codename
  • On submit page, empty submissions produce flashed message
  • On submit page, short message submitted successfully
  • On submit page, file greater than 500 MB produces "The connection was reset" in Tor Browser quickly before the entire file is uploaded
  • On submit page, file less than 500 MB submitted successfully
Returning source base cases
  • Nonexistent codename cannot log in
  • Empty codename cannot log in
  • Legitimate codename can log in
  • Returning user can view journalist replies - need to log into journalist interface to test

Journalist Interface

Login base cases
  • Can log in with 2FA tokens
  • incorrect password cannot log in
  • invalid 2fa token cannot log in
  • 2fa immediate reuse cannot log in
  • (DID NOT TEST) Journalist account with HOTP can log in
Index base cases

N.B. If you previously used "Safest" mode in Tor Browser as a Source, you'll have to re-enable JS to verify some of the functionality below.

  • Filter by codename works
  • Starring and unstarring works
  • Click select all selects all submissions
  • Selecting all and clicking "Download" works
Individual source page
  • You can submit a reply and a flashed message and new row appears
  • You cannot submit an empty reply
  • Clicking "Delete Source Account" and the source and docs are deleted
  • You can click on a document and successfully decrypt using application private key

2.1.0 release-specific changes

Web Applications

  • #6075 - valid HTML time tags

    • Verify that the SI lookup page HTML validates correctly (e.g. by using a Firefox addon)
  • #5695 - scrypt and sessions refactor

    • Verify that no codename- or session-related errors were observed during application acceptance testing
    • On the Source Interface, create a new user, log out, return to the home page without clearing the Tor Browser identity, and repeat this process at least 5 times. Verify that you do not observe an error wwith the text "you have been logged out due to inactivity" while repeating these steps.
    • If possible, log in to the Source Interface simultaneously as separate users from multiple instances of the Tor Browser, submit messages from each user, and reply to the users from the Journalist Interface.
      • Verify that each user has a distinct codename in the SI and journalist designation in the JI
      • Verify that no errors were observed on the Source Interface in any user session
      • Verify that submitted messages appear on the correct source page on the JI
      • Verify that sources receive the correct reply in the SI and that the reply can be read
      • On the application server, verify that each source has a distinct GPG key fingerprint, e.g. with the command sudo -u www-data gpg --homedir /var/lib/securedrop/keys -k | grep "Source Key" | wc -l ; that number matches the number of sources I see in the JI
    • N/A - (Upgrade-only) Verify that a source user created before the upgrade can still log in using their existing codename, that their journalist designation is unchanged, and that they can still read existing and new replies
  • #6041 - SI a11y refactor

    1. Option 1, low effort: review automated accessibility checks
      • Verify that Source Interface functionality and appearance is unchanged
      • Using the Web Developer tools' Accessibility Inspector and check for issues: All, verify that accessibility issues have been significantly reduced on the SI /lookup page vs the corresponding page on https://demo-source.securedrop.org
        • Demo instance is now running v2.1.0~rc1, so I didn't compare. SF/NY instance is down, so I can't compare previous prod easily, either. =( )
        • Instead, I reviewed the output for "Check for issues: All issues" on multiple SI pages. In all cases, I received only a notice about "Keyboard: Focusable elements should have interactive semantics". No other issues found.
      • Compare other pages on the SI in the same way - verify that issues have been removed and no new issues introduced.
        • Same caveats re: methodology as above, but still, looks good.
    2. Option 2, medium effort: step through submission flow in screen reader
      • On Tails, activate the screen reader from the accessibility menu
      • Step through the submission flow at https://demo-source.securedrop.org and pay attention to how the experience feels
        • See caveats above: demo now has same code, so didn't bother with comparison
      • Step through the submission flow in your QA instance and compare how the experience feels
        • Aside from the comical reading aloud of a v3 onion url, the experience was remarkably smooth. I lack experience with screen readers, but even states like "Alert: Thanks! We received your message" were read aloud to me after submission, which is pretty cool.
  • #5958 - OTP secret length

    • verify that the TOTP secret and corresponding QRcode displayed via manage.py add-admin on the application server are 32 chars long and can be used to generate valid TOTP codes via the Google Authenticator mobile app
    • verify that the TOTP secret and QRCode generated via the JI during user creation are also 32 chars long and valid
    • [ skipped subsequent upgrade-only tests]
  • #5696 - no JS in user delete modal

    • Set the Tor Browser security level to Safest
    • In the Journalist Interface, verify that a confirmation modal dialog is displayed when a user is selected for deletion
    • Choose Cancel and verify that the user was not deleted
    • Select the user for deletion again and choose Delete. Verify that the user was deleted.
  • #5988 - TLS 1.3 only

    • (HTTPS-only) On the Source Interface index page, verify via the Tor Browser Security Tab that the connection is encrypted not only as an Onion Service but also vi TLS 1.3 with 256-bit keys
    • (HTTPS-only) On the application server, inspect /etc/apache2/sites-enabled/source.conf and verify that it contains the line SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
    • Install testssl.sh in Tails: sudo apt install -y testssl.sh -t buster-backports, then run testssl.sh <onion url>. Confirm that only TLSv1.3 is provided; all earlier protos are not.
  • #5979 - Remove expired key

    • ❌ verify that the old signing key with fingerprint 22245C81E3BAEB4138B36061310F561200F4AD77 is not present on the application or monitor servers, e.g. by running the command for s in app mon; do ssh $s sudo apt-key list from the Admin workstation
    • Verify that the new signing key 2359 E653 8C06 13E6 5295 5E6C 188E DD3B 7B22 E6A3 is presenton the servers
  • #6107 - Mute fwupd alerts

    • Verify that no OSSEC alerts related to fwupd were received
  • #5909 - Manually-transferred backup

    • Back up the instance using ./securedrop-admin backup
    • Copy the backup file to the application server using e.g. scp sd-backup.tar.gz app:/tmp/sd-backup-transfer.tar.gz
    • Verify that the command ./securedrop-admin restore --no-transfer sd-backup-transfer.tar.gz completes successfully, and that the local backup file is not transferred to the server during the playbook run.
  • #6110 - Repair Tails installer

    • (Tails version 4.18 or lesser):
      • Did not test, as I'm using Tails v4.23

@zenmonkeykstop
Copy link
Contributor

zenmonkeykstop commented Oct 9, 2021

@cfm: re the manual transfer test, the playbook does require that the tarball be available locally as well to verify that it is valid - good catch on the docs side, this option hasn't been documented yet and should be for the release.

@conorsch
Copy link
Contributor

Updated the OP with mention of 2.1.0~rc2. @cfm if you've got cycles today, simply re-testing the problematic sections of your previous testing report on rc1 would be ideal. For simplicity's sake, I'd say take the VM upgrade scenario again, and I'll take clean install VMs again and post results.

@conorsch
Copy link
Contributor

So far so good on 2.1.0~rc2. One issue of note is that on the clean install scenario, I observed an apt-update failure:

sd-qa-2 1 0-rc2-apt-failure-1

It appears the order of operations is as follows:

  1. All apt packages updated in prepare-servers, as expected
  2. apt-test key is added via local modifications of the install-fpf-repo vars, as part of QA
  3. apt-test repo URL is added via local modifications of the install-fpf-repo vars, as part of QA
  4. the securedrop-keyring package is installed from apt-test, which clobbers the test key with the prod key upon installation
  5. the common role fails to update apt lists again, since the apt-test repo is configured but the key is removed

This issue only affects QA testing, it isn't a problem for prod. But I'm documenting it here in case other testers encounter it.

@cfm
Copy link
Member

cfm commented Oct 15, 2021

All issues raised about 2.1.0~rc1 in #6103 (comment) are resolved in 2.1.0~rc2. Still outstanding against 2.1.0~rc2:

amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin verify
=========================== short test summary info ============================
FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor-utils]
FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor] - para...
FAILED app/test_apparmor.py::test_apparmor_apache_capabilities[paramiko:/app-dac_override]
FAILED app/test_ossec_agent.py::test_hosts_files[paramiko:/app] - AssertionEr...
FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/app]
FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/mon]
FAILED common/test_automatic_updates.py::test_unattended_upgrades_functional[paramiko:/app]
FAILED mon/test_ossec_server.py::test_ossec_connectivity[paramiko:/mon] - Ass...
FAILED mon/test_ossec_server.py::test_hosts_files[paramiko:/mon] - AssertionE...
FAILED mon/test_postfix.py::test_postfix_generic_maps[paramiko:/mon] - Assert...
= 10 failed, 425 passed, 7 skipped, 3 xfailed, 1 xpassed, 10 warnings in 2016.78s (0:33:36) =
[...]
=================================== FAILURES ===================================
_______________ test_apparmor_pkg[paramiko://app-apparmor-utils] _______________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

self = <testinfra.backend.paramiko.ParamikoBackend object at 0x768793cc3588>
command = b'uname -s', args = (), kwargs = {}

    def run(self, command, *args, **kwargs):
        command = self.get_command(command, *args)
        command = self.encode(command)
        try:
>           rc, stdout, stderr = self._exec_command(command)

../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:115: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <testinfra.backend.paramiko.ParamikoBackend object at 0x768793cc3588>
command = b'uname -s'

    def _exec_command(self, command):
>       chan = self.client.get_transport().open_session()

../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:102: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <testinfra.utils.cached_property object at 0x768793cc3860>
obj = <testinfra.backend.paramiko.ParamikoBackend object at 0x768793cc3588>
cls = <class 'testinfra.backend.paramiko.ParamikoBackend'>

    def __get__(self, obj, cls):
        if obj is None:
            return self
>       value = obj.__dict__[self.func.__name__] = self.func(obj)

../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <testinfra.backend.paramiko.ParamikoBackend object at 0x768793cc3588>

    @cached_property
    def client(self):
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.WarningPolicy())
        cfg = {
            "hostname": self.host.name,
            "port": int(self.host.port) if self.host.port else 22,
            "username": self.host.user,
            "timeout": self.timeout,
        }
        if self.ssh_config:
            with open(self.ssh_config) as f:
                ssh_config = paramiko.SSHConfig()
                ssh_config.parse(f)
                self._load_ssh_config(client, cfg, ssh_config)
        else:
            # fallback reading ~/.ssh/config
            default_ssh_config = os.path.join(
                os.path.expanduser('~'), '.ssh', 'config')
            try:
                with open(default_ssh_config) as f:
                    ssh_config = paramiko.SSHConfig()
                    ssh_config.parse(f)
            except IOError:
                pass
            else:
                self._load_ssh_config(client, cfg, ssh_config)
    
        if self.ssh_identity_file:
            cfg["key_filename"] = self.ssh_identity_file
>       client.connect(**cfg)

../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <paramiko.client.SSHClient object at 0x76879240a278>
hostname = 'sypudnu7biyq3cxacrgohjwpveeabi7inrtxop2543674siadyxhrqqd.onion'
port = 22, username = 'vagrant', password = None, pkey = None
key_filename = None, timeout = 10, allow_agent = True, look_for_keys = True
compress = False, sock = <paramiko.proxy.ProxyCommand object at 0x76879240cef0>
gss_auth = False, gss_kex = False, gss_deleg_creds = True, gss_host = None
banner_timeout = None, auth_timeout = None, gss_trust_dns = True
passphrase = None, disabled_algorithms = None

    def connect(
        self,
        hostname,
        port=SSH_PORT,
        username=None,
        password=None,
        pkey=None,
        key_filename=None,
        timeout=None,
        allow_agent=True,
        look_for_keys=True,
        compress=False,
        sock=None,
        gss_auth=False,
        gss_kex=False,
        gss_deleg_creds=True,
        gss_host=None,
        banner_timeout=None,
        auth_timeout=None,
        gss_trust_dns=True,
        passphrase=None,
        disabled_algorithms=None,
    ):
        """
        Connect to an SSH server and authenticate to it.  The server's host key
        is checked against the system host keys (see `load_system_host_keys`)
        and any local host keys (`load_host_keys`).  If the server's hostname
        is not found in either set of host keys, the missing host key policy
        is used (see `set_missing_host_key_policy`).  The default policy is
        to reject the key and raise an `.SSHException`.
    
        Authentication is attempted in the following order of priority:
    
            - The ``pkey`` or ``key_filename`` passed in (if any)
    
              - ``key_filename`` may contain OpenSSH public certificate paths
                as well as regular private-key paths; when files ending in
                ``-cert.pub`` are found, they are assumed to match a private
                key, and both components will be loaded. (The private key
                itself does *not* need to be listed in ``key_filename`` for
                this to occur - *just* the certificate.)
    
            - Any key we can find through an SSH agent
            - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
              ``~/.ssh/``
    
              - When OpenSSH-style public certificates exist that match an
                existing such private key (so e.g. one has ``id_rsa`` and
                ``id_rsa-cert.pub``) the certificate will be loaded alongside
                the private key and used for authentication.
    
            - Plain username/password auth, if a password was given
    
        If a private key requires a password to unlock it, and a password is
        passed in, that password will be used to attempt to unlock the key.
    
        :param str hostname: the server to connect to
        :param int port: the server port to connect to
        :param str username:
            the username to authenticate as (defaults to the current local
            username)
        :param str password:
            Used for password authentication; is also used for private key
            decryption if ``passphrase`` is not given.
        :param str passphrase:
            Used for decrypting private keys.
        :param .PKey pkey: an optional private key to use for authentication
        :param str key_filename:
            the filename, or list of filenames, of optional private key(s)
            and/or certs to try for authentication
        :param float timeout:
            an optional timeout (in seconds) for the TCP connect
        :param bool allow_agent:
            set to False to disable connecting to the SSH agent
        :param bool look_for_keys:
            set to False to disable searching for discoverable private key
            files in ``~/.ssh/``
        :param bool compress: set to True to turn on compression
        :param socket sock:
            an open socket or socket-like object (such as a `.Channel`) to use
            for communication to the target host
        :param bool gss_auth:
            ``True`` if you want to use GSS-API authentication
        :param bool gss_kex:
            Perform GSS-API Key Exchange and user authentication
        :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
        :param str gss_host:
            The targets name in the kerberos database. default: hostname
        :param bool gss_trust_dns:
            Indicates whether or not the DNS is trusted to securely
            canonicalize the name of the host being connected to (default
            ``True``).
        :param float banner_timeout: an optional timeout (in seconds) to wait
            for the SSH banner to be presented.
        :param float auth_timeout: an optional timeout (in seconds) to wait for
            an authentication response.
        :param dict disabled_algorithms:
            an optional dict passed directly to `.Transport` and its keyword
            argument of the same name.
    
        :raises:
            `.BadHostKeyException` -- if the server's host key could not be
            verified
        :raises: `.AuthenticationException` -- if authentication failed
        :raises:
            `.SSHException` -- if there was any other error connecting or
            establishing an SSH session
        :raises socket.error: if a socket error occurred while connecting
    
        .. versionchanged:: 1.15
            Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
            ``gss_deleg_creds`` and ``gss_host`` arguments.
        .. versionchanged:: 2.3
            Added the ``gss_trust_dns`` argument.
        .. versionchanged:: 2.4
            Added the ``passphrase`` argument.
        .. versionchanged:: 2.6
            Added the ``disabled_algorithms`` argument.
        """
        if not sock:
            errors = {}
            # Try multiple possible address families (e.g. IPv4 vs IPv6)
            to_try = list(self._families_and_addresses(hostname, port))
            for af, addr in to_try:
                try:
                    sock = socket.socket(af, socket.SOCK_STREAM)
                    if timeout is not None:
                        try:
                            sock.settimeout(timeout)
                        except:
                            pass
                    retry_on_signal(lambda: sock.connect(addr))
                    # Break out of the loop on success
                    break
                except socket.error as e:
                    # Raise anything that isn't a straight up connection error
                    # (such as a resolution error)
                    if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
                        raise
                    # Capture anything else so we know how the run looks once
                    # iteration is complete. Retain info about which attempt
                    # this was.
                    errors[addr] = e
    
            # Make sure we explode usefully if no address family attempts
            # succeeded. We've no way of knowing which error is the "right"
            # one, so we construct a hybrid exception containing all the real
            # ones, of a subclass that client code should still be watching for
            # (socket.error)
            if len(errors) == len(to_try):
                raise NoValidConnectionsError(errors)
    
        t = self._transport = Transport(
            sock,
            gss_kex=gss_kex,
            gss_deleg_creds=gss_deleg_creds,
            disabled_algorithms=disabled_algorithms,
        )
        t.use_compression(compress=compress)
        t.set_gss_host(
            # t.hostname may be None, but GSS-API requires a target name.
            # Therefore use hostname as fallback.
            gss_host=gss_host or hostname,
            trust_dns=gss_trust_dns,
            gssapi_requested=gss_auth or gss_kex,
        )
        if self._log_channel is not None:
            t.set_log_channel(self._log_channel)
        if banner_timeout is not None:
            t.banner_timeout = banner_timeout
        if auth_timeout is not None:
            t.auth_timeout = auth_timeout
    
        if port == SSH_PORT:
            server_hostkey_name = hostname
        else:
            server_hostkey_name = "[{}]:{}".format(hostname, port)
        our_server_keys = None
    
        our_server_keys = self._system_host_keys.get(server_hostkey_name)
        if our_server_keys is None:
            our_server_keys = self._host_keys.get(server_hostkey_name)
        if our_server_keys is not None:
            keytype = our_server_keys.keys()[0]
            sec_opts = t.get_security_options()
            other_types = [x for x in sec_opts.key_types if x != keytype]
            sec_opts.key_types = [keytype] + other_types
    
        t.start_client(timeout=timeout)
    
        # If GSS-API Key Exchange is performed we are not required to check the
        # host key, because the host is authenticated via GSS-API / SSPI as
        # well as our client.
        if not self._transport.gss_kex_used:
>           server_key = t.get_remote_server_key()

../../admin/.venv3/lib/python3.7/site-packages/paramiko/client.py:412: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <paramiko.Transport at 0x924202b0 (cipher aes128-ctr, 128 bits) (connected; awaiting auth)>

    def get_remote_server_key(self):
        """
        Return the host key of the server (in client mode).
    
        .. note::
            Previously this call returned a tuple of ``(key type, key
            string)``. You can get the same effect by calling `.PKey.get_name`
            for the key type, and ``str(key)`` for the key string.
    
        :raises: `.SSHException` -- if no session is currently active.
    
        :return: public key (`.PKey`) of the remote server
        """
        if (not self.active) or (not self.initial_kex_done):
>           raise SSHException("No existing session")
E           paramiko.ssh_exception.SSHException: No existing session

../../admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py:834: SSHException

During handling of the above exception, another exception occurred:

host = <testinfra.host.Host paramiko://app>, pkg = 'apparmor-utils'

    @pytest.mark.parametrize('pkg', ['apparmor', 'apparmor-utils'])
    def test_apparmor_pkg(host, pkg):
        """ Apparmor package dependencies """
>       assert host.package(pkg).is_installed

app/test_apparmor.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../admin/.venv3/lib/python3.7/site-packages/testinfra/host.py:111: in __getattr__
    obj = module_class.get_module(self)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/base.py:19: in get_module
    klass = cls.get_module_class(_host)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/package.py:65: in get_module_class
    if host.system_info.type == 'windows':
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/systeminfo.py:144: in type
    return self.sysinfo["type"]
../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/systeminfo.py:31: in sysinfo
    uname = self.run_expect([0, 1], 'uname -s')
../../admin/.venv3/lib/python3.7/site-packages/testinfra/host.py:75: in run
    return self.backend.run(command, *args, **kwargs)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:117: in run
    if not self.client.get_transport().is_active():
../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:98: in client
    client.connect(**cfg)
../../admin/.venv3/lib/python3.7/site-packages/paramiko/client.py:412: in connect
    server_key = t.get_remote_server_key()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <paramiko.Transport at 0x92420d30 (connecting)>

    def get_remote_server_key(self):
        """
        Return the host key of the server (in client mode).
    
        .. note::
            Previously this call returned a tuple of ``(key type, key
            string)``. You can get the same effect by calling `.PKey.get_name`
            for the key type, and ``str(key)`` for the key string.
    
        :raises: `.SSHException` -- if no session is currently active.
    
        :return: public key (`.PKey`) of the remote server
        """
        if (not self.active) or (not self.initial_kex_done):
>           raise SSHException("No existing session")
E           paramiko.ssh_exception.SSHException: No existing session

../../admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py:834: SSHException
__________________ test_apparmor_pkg[paramiko://app-apparmor] __________________
[gw0] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://app>, pkg = 'apparmor'

    @pytest.mark.parametrize('pkg', ['apparmor', 'apparmor-utils'])
    def test_apparmor_pkg(host, pkg):
        """ Apparmor package dependencies """
>       assert host.package(pkg).is_installed

app/test_apparmor.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../admin/.venv3/lib/python3.7/site-packages/testinfra/host.py:111: in __getattr__
    obj = module_class.get_module(self)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/base.py:19: in get_module
    klass = cls.get_module_class(_host)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/package.py:65: in get_module_class
    if host.system_info.type == 'windows':
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/systeminfo.py:144: in type
    return self.sysinfo["type"]
../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/modules/systeminfo.py:31: in sysinfo
    uname = self.run_expect([0, 1], 'uname -s')
../../admin/.venv3/lib/python3.7/site-packages/testinfra/host.py:75: in run
    return self.backend.run(command, *args, **kwargs)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:115: in run
    rc, stdout, stderr = self._exec_command(command)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:102: in _exec_command
    chan = self.client.get_transport().open_session()
../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:98: in client
    client.connect(**cfg)
../../admin/.venv3/lib/python3.7/site-packages/paramiko/client.py:412: in connect
    server_key = t.get_remote_server_key()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <paramiko.Transport at 0x75e31860 (cipher aes128-ctr, 128 bits) (connected; awaiting auth)>

    def get_remote_server_key(self):
        """
        Return the host key of the server (in client mode).
    
        .. note::
            Previously this call returned a tuple of ``(key type, key
            string)``. You can get the same effect by calling `.PKey.get_name`
            for the key type, and ``str(key)`` for the key string.
    
        :raises: `.SSHException` -- if no session is currently active.
    
        :return: public key (`.PKey`) of the remote server
        """
        if (not self.active) or (not self.initial_kex_done):
>           raise SSHException("No existing session")
E           paramiko.ssh_exception.SSHException: No existing session

../../admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py:834: SSHException
________ test_apparmor_apache_capabilities[paramiko://app-dac_override] ________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://app>, cap = 'dac_override'

    @pytest.mark.parametrize('cap', apache2_capabilities)
    def test_apparmor_apache_capabilities(host, cap):
        """ check for exact list of expected app-armor capabilities for apache2 """
        c = host.run(
>           r"perl -nE '/^\s+capability\s+(\w+),$/ && say $1' /etc/apparmor.d/usr.sbin.apache2"
        )

app/test_apparmor.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../admin/.venv3/lib/python3.7/site-packages/testinfra/host.py:75: in run
    return self.backend.run(command, *args, **kwargs)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:115: in run
    rc, stdout, stderr = self._exec_command(command)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:102: in _exec_command
    chan = self.client.get_transport().open_session()
../../admin/.venv3/lib/python3.7/site-packages/testinfra/utils/__init__.py:29: in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
../../admin/.venv3/lib/python3.7/site-packages/testinfra/backend/paramiko.py:98: in client
    client.connect(**cfg)
../../admin/.venv3/lib/python3.7/site-packages/paramiko/client.py:412: in connect
    server_key = t.get_remote_server_key()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <paramiko.Transport at 0x90b0def0 (unconnected)>

    def get_remote_server_key(self):
        """
        Return the host key of the server (in client mode).
    
        .. note::
            Previously this call returned a tuple of ``(key type, key
            string)``. You can get the same effect by calling `.PKey.get_name`
            for the key type, and ``str(key)`` for the key string.
    
        :raises: `.SSHException` -- if no session is currently active.
    
        :return: public key (`.PKey`) of the remote server
        """
        if (not self.active) or (not self.initial_kex_done):
>           raise SSHException("No existing session")
E           paramiko.ssh_exception.SSHException: No existing session

../../admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py:834: SSHException
------------------------------ Captured log call -------------------------------
ERROR    paramiko.transport:transport.py:1819 Exception: Error reading SSH protocol banner
ERROR    paramiko.transport:transport.py:1817 Traceback (most recent call last):
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2211, in _check_banner
ERROR    paramiko.transport:transport.py:1817     buf = self.packetizer.readline(timeout)
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/packet.py", line 380, in readline
ERROR    paramiko.transport:transport.py:1817     buf += self._read_timeout(timeout)
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/packet.py", line 622, in _read_timeout
ERROR    paramiko.transport:transport.py:1817     raise socket.timeout()
ERROR    paramiko.transport:transport.py:1817 socket.timeout
ERROR    paramiko.transport:transport.py:1817 
ERROR    paramiko.transport:transport.py:1817 During handling of the above exception, another exception occurred:
ERROR    paramiko.transport:transport.py:1817 
ERROR    paramiko.transport:transport.py:1817 Traceback (most recent call last):
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2039, in run
ERROR    paramiko.transport:transport.py:1817     self._check_banner()
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2216, in _check_banner
ERROR    paramiko.transport:transport.py:1817     "Error reading SSH protocol banner" + str(e)
ERROR    paramiko.transport:transport.py:1817 paramiko.ssh_exception.SSHException: Error reading SSH protocol banner
ERROR    paramiko.transport:transport.py:1817 
ERROR    paramiko.transport:transport.py:1819 Exception: Error reading SSH protocol banner
ERROR    paramiko.transport:transport.py:1817 Traceback (most recent call last):
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2211, in _check_banner
ERROR    paramiko.transport:transport.py:1817     buf = self.packetizer.readline(timeout)
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/packet.py", line 380, in readline
ERROR    paramiko.transport:transport.py:1817     buf += self._read_timeout(timeout)
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/packet.py", line 622, in _read_timeout
ERROR    paramiko.transport:transport.py:1817     raise socket.timeout()
ERROR    paramiko.transport:transport.py:1817 socket.timeout
ERROR    paramiko.transport:transport.py:1817 
ERROR    paramiko.transport:transport.py:1817 During handling of the above exception, another exception occurred:
ERROR    paramiko.transport:transport.py:1817 
ERROR    paramiko.transport:transport.py:1817 Traceback (most recent call last):
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2039, in run
ERROR    paramiko.transport:transport.py:1817     self._check_banner()
ERROR    paramiko.transport:transport.py:1817   File "/home/amnesia/Persistent/securedrop/admin/.venv3/lib/python3.7/site-packages/paramiko/transport.py", line 2216, in _check_banner
ERROR    paramiko.transport:transport.py:1817     "Error reading SSH protocol banner" + str(e)
ERROR    paramiko.transport:transport.py:1817 paramiko.ssh_exception.SSHException: Error reading SSH protocol banner
ERROR    paramiko.transport:transport.py:1817
_______________________ test_hosts_files[paramiko://app] _______________________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://app>

    def test_hosts_files(host):
        """ Ensure host files mapping are in place """
        f = host.file('/etc/hosts')
    
        mon_ip = os.environ.get('MON_IP', sdvars.mon_ip)
        mon_host = sdvars.monitor_hostname
    
        assert f.contains(r'^127.0.0.1\s*localhost')
>       assert f.contains(r'^{}\s*{}\s*securedrop-monitor-server-alias$'.format(
                                                                        mon_ip,
                                                                        mon_host))
E       AssertionError: assert False
E        +  where False = <bound method File.contains of <file /etc/hosts>>('^192.168.121.58\\s*mon\\s*securedrop-monitor-server-alias$')
E        +    where <bound method File.contains of <file /etc/hosts>> = <file /etc/hosts>.contains
E        +    and   '^192.168.121.58\\s*mon\\s*securedrop-monitor-server-alias$' = <built-in method format of str object at 0x768792d3b330>('192.168.121.58', 'mon')
E        +      where <built-in method format of str object at 0x768792d3b330> = '^{}\\s*{}\\s*securedrop-monitor-server-alias$'.format

app/test_ossec_agent.py:19: AssertionError
__________________ test_fpf_apt_repo_present[paramiko://app] ___________________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://app>

    def test_fpf_apt_repo_present(host):
        """
        Ensure the FPF apt repo, apt.freedom.press, is configured.
        This repository is necessary for the SecureDrop Debian packages,
        including:
    
          * securedrop-app-code
          * securedrop-keyring
          * securedrop-grsec
    
        Depending on the host, additional FPF-maintained packages will be
        installed, e.g. for OSSEC. Install state for those packages
        is tested separately.
        """
    
        # If the var fpf_apt_repo_url test var is apt-test, validate that the
        # apt repository is configured on the host
        if test_vars.fpf_apt_repo_url == "https://apt-test.freedom.press":
            f = host.file('/etc/apt/sources.list.d/apt_test_freedom_press.list')
        else:
            f = host.file('/etc/apt/sources.list.d/apt_freedom_press.list')
        repo_regex = r'^deb \[arch=amd64\] {} {} main$'.format(
                          re.escape(test_vars.fpf_apt_repo_url),
                          re.escape(host.system_info.codename))
>       assert f.contains(repo_regex)
E       AssertionError: assert False
E        +  where False = <bound method File.contains of <file /etc/apt/sources.list.d/apt_freedom_press.list>>('^deb \\[arch=amd64\\] https://apt\\.freedom\\.press focal main$')
E        +    where <bound method File.contains of <file /etc/apt/sources.list.d/apt_freedom_press.list>> = <file /etc/apt/sources.list.d/apt_freedom_press.list>.contains

common/test_fpf_apt_repo.py:35: AssertionError
__________________ test_fpf_apt_repo_present[paramiko://mon] ___________________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://mon>

    def test_fpf_apt_repo_present(host):
        """
        Ensure the FPF apt repo, apt.freedom.press, is configured.
        This repository is necessary for the SecureDrop Debian packages,
        including:
    
          * securedrop-app-code
          * securedrop-keyring
          * securedrop-grsec
    
        Depending on the host, additional FPF-maintained packages will be
        installed, e.g. for OSSEC. Install state for those packages
        is tested separately.
        """
    
        # If the var fpf_apt_repo_url test var is apt-test, validate that the
        # apt repository is configured on the host
        if test_vars.fpf_apt_repo_url == "https://apt-test.freedom.press":
            f = host.file('/etc/apt/sources.list.d/apt_test_freedom_press.list')
        else:
            f = host.file('/etc/apt/sources.list.d/apt_freedom_press.list')
        repo_regex = r'^deb \[arch=amd64\] {} {} main$'.format(
                          re.escape(test_vars.fpf_apt_repo_url),
                          re.escape(host.system_info.codename))
>       assert f.contains(repo_regex)
E       AssertionError: assert False
E        +  where False = <bound method File.contains of <file /etc/apt/sources.list.d/apt_freedom_press.list>>('^deb \\[arch=amd64\\] https://apt\\.freedom\\.press focal main$')
E        +    where <bound method File.contains of <file /etc/apt/sources.list.d/apt_freedom_press.list>> = <file /etc/apt/sources.list.d/apt_freedom_press.list>.contains

common/test_fpf_apt_repo.py:35: AssertionError
_____________ test_unattended_upgrades_functional[paramiko://app] ______________
[gw0] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://app>

    def test_unattended_upgrades_functional(host):
        """
        Ensure unatteded-upgrades completes successfully and ensures all packages
        are up-to-date.
        """
        c = host.run('sudo unattended-upgrades --dry-run --debug')
        assert c.rc == 0
        expected_origins = (
            "Allowed origins are: origin=Ubuntu,archive=focal, origin=Ubuntu,archive=focal-security"
            ", origin=Ubuntu,archive=focal-updates, origin=SecureDrop,codename=focal"
        )
        expected_result = (
            "No packages found that can be upgraded unattended and no pending auto-removals"
        )
    
        assert expected_origins in c.stdout
>       assert expected_result in c.stdout
E       assert 'No packages found that can be upgraded unattended and no pending auto-removals' in "Starting unattended upgrades script\nAllowed origins are: origin=Ubuntu,archive=focal, origin=Ubuntu,archive=focal-se...ades installed\nInstCount=0 DelCount=0 BrokenCount=0\nThe list of kept packages can't be calculated in dry-run mode.\n"
E        +  where "Starting unattended upgrades script\nAllowed origins are: origin=Ubuntu,archive=focal, origin=Ubuntu,archive=focal-se...ades installed\nInstCount=0 DelCount=0 BrokenCount=0\nThe list of kept packages can't be calculated in dry-run mode.\n" = CommandResult(command=b'sudo unattended-upgrades --dry-run --debug', exit_status=0, stdout=b"Starting unattended upgra.../usr/bin/dpkg --force-confdef --force-confold --force-confdef --force-confold --status-fd 10 --configure --pending \n').stdout

common/test_automatic_updates.py:130: AssertionError
___________________ test_ossec_connectivity[paramiko://mon] ____________________
[gw0] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://mon>

    def test_ossec_connectivity(host):
        """
        Ensure ossec-server machine has active connection to the ossec-agent.
        The ossec service will report all available agents, and we can inspect
        that list to make sure it's the host we expect.
        """
        desired_output = "{}-{} is available.".format(
            securedrop_test_vars.app_hostname,
            os.environ.get('APP_IP', securedrop_test_vars.app_ip))
        with host.sudo():
            c = host.check_output("/var/ossec/bin/list_agents -a")
>           assert c == desired_output
E           AssertionError: assert 'app-prod-192...is available.' == 'app-192.168....is available.'
E             - app-192.168.121.148 is available.
E             + app-prod-192.168.121.148 is available.
E             ?    +++++

mon/test_ossec_server.py:22: AssertionError
_______________________ test_hosts_files[paramiko://mon] _______________________
[gw1] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://mon>

    def test_hosts_files(host):
        """ Ensure host files mapping are in place """
        f = host.file('/etc/hosts')
    
        app_ip = os.environ.get('APP_IP', securedrop_test_vars.app_ip)
        app_host = securedrop_test_vars.app_hostname
    
        assert f.contains('^127.0.0.1.*localhost')
>       assert f.contains(r'^{}\s*{}$'.format(app_ip, app_host))
E       AssertionError: assert False
E        +  where False = <bound method File.contains of <file /etc/hosts>>('^192.168.121.148\\s*app$')
E        +    where <bound method File.contains of <file /etc/hosts>> = <file /etc/hosts>.contains
E        +    and   '^192.168.121.148\\s*app$' = <built-in method format of str object at 0x768792499b30>('192.168.121.148', 'app')
E        +      where <built-in method format of str object at 0x768792499b30> = '^{}\\s*{}$'.format

mon/test_ossec_server.py:89: AssertionError
__________________ test_postfix_generic_maps[paramiko://mon] ___________________
[gw0] linux -- Python 3.7.3 /home/amnesia/Persistent/securedrop/admin/.venv3/bin/python3

host = <testinfra.host.Host paramiko://mon>

    def test_postfix_generic_maps(host):
        """
        Check configuration of Postfix generic map when sasl_domain is set
        and ossec_from_address is not specified.
        """
        assert host.file("/etc/postfix/generic").exists
>       assert host.file("/etc/postfix/generic").contains(
            "^ossec@{} {}@{}".format(
                securedrop_test_vars.monitor_hostname,
                securedrop_test_vars.sasl_username,
                securedrop_test_vars.sasl_domain,
            )
        )
E       AssertionError: assert False
E        +  where False = <bound method File.contains of <file /etc/postfix/generic>>('^ossec@mon [email protected]')
E        +    where <bound method File.contains of <file /etc/postfix/generic>> = <file /etc/postfix/generic>.contains
E        +      where <file /etc/postfix/generic> = <class 'testinfra.modules.base.GNUFile'>('/etc/postfix/generic')
E        +        where <class 'testinfra.modules.base.GNUFile'> = <testinfra.host.Host paramiko://mon>.file
E        +    and   '^ossec@mon [email protected]' = <built-in method format of str object at 0x7c8675ecd870>('mon', 'tuzmjvur', 'riseup.net')
E        +      where <built-in method format of str object at 0x7c8675ecd870> = '^ossec@{} {}@{}'.format
E        +      and   'mon' = {}.monitor_hostname
E        +      and   'tuzmjvur' = {}.sasl_username
E        +      and   'riseup.net' = {}.sasl_domain

mon/test_postfix.py:36: AssertionError
=========================== short test summary info ============================
FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor-utils]
FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor] - para...
FAILED app/test_apparmor.py::test_apparmor_apache_capabilities[paramiko:/app-dac_override]
FAILED app/test_ossec_agent.py::test_hosts_files[paramiko:/app] - AssertionEr...
FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/app]
FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/mon]
FAILED common/test_automatic_updates.py::test_unattended_upgrades_functional[paramiko:/app]
FAILED mon/test_ossec_server.py::test_ossec_connectivity[paramiko:/mon] - Ass...
FAILED mon/test_ossec_server.py::test_hosts_files[paramiko:/mon] - AssertionE...
FAILED mon/test_postfix.py::test_postfix_generic_maps[paramiko:/mon] - Assert...
= 10 failed, 425 passed, 7 skipped, 3 xfailed, 1 xpassed, 10 warnings in 2016.78s (0:33:36) =
--------

Test plan and results

  • If you are testing the upgrade scenario, you should create source and journalist accounts before performing the upgrade - some test cases require existing accounts.

  • In order to test Add support for support TLSv1.3 ciphersuites as for #4769 #5988 you will need to enable HTTPS on the source interface. To do so using self-signed certs:

    • Use the command cd ~/Persistent/securedrop && make self-signed-https-certs on the Admin Workstation to generate the necessary files
    • Choose the appropriate options while running ./securedrop-admin sdconfig
    • Accept the self-signed cert when prompted with the HTTPS warning in Tor Browser.

Environment

  • Install target: VMs
  • Tails version: 4.22
  • Test Scenario: Upgrade (HTTPS enabled)
  • SSH over Tor: yes
  • Release candidate: 2.1.0~rc2
  • General notes:
  1. Take a backup of your existing SecureDrop installation using the securedrop-admin backup command. You will need enough free space on your Admin Workstation USB to complete this backup.

  2. Either run the ansible QA playbook from qa-update-playbook (pending updated QA repo playbook for Focal #6123):

    # Steps to use this playbook from an Admin Workstation:
    #
    # 1. Check out the current production release tag.
    # 2. Provision a SecureDrop instance (hardware or VMs).
    # 4. Run `./securedrop-admin tailsconfig`
    # 5. Run `source admin/.venv3/bin/activate` (so ansible commands work)
    # 6. Run `cd install_files/ansible-base`
    # 7. Run `ansible-playbook -vv --diff securedrop-qa.yml`
    # 8. `ssh app` # start interactive session
    # 9. On the Application Server, run `sudo unattended-upgrades -d`
    # 10. Reboot the server once the upgrade is complete
    # 11. Repeat steps 8-10 on the Monitor server

Retesting only those cases that failed or raised questions in #6103 (comment)...

2.1.0 release-specific changes

  • #6041 - SI a11y refactor

    1. [-] Option 1, low effort: review automated accessibility checks
      • [-] Verify that Source Interface functionality and appearance is unchanged
      • [-] Using the Web Developer tools' Accessibility Inspector and check for issues: All, verify that accessibility issues have been significantly reduced on the SI /lookup page vs the corresponding page on https://demo-source.securedrop.org
      • [-] Compare other pages on the SI in the same way - verify that issues have been removed and no new issues introduced.
    2. Option 2, medium effort: step through submission flow in screen reader
      • On Tails, activate the screen reader from the accessibility menu
      • Step through the submission flow at https://demo-source.securedrop.org and pay attention to how the experience feels
      • Step through the submission flow in your QA instance and compare how the experience feels
  • #5988 - TLS 1.3 only

    • (HTTPS-only) On the Source Interface index page, verify via the Tor Browser Security Tab that the connection is encrypted not only as an Onion Service but also via TLS 1.3
    • (HTTPS-only) On the application server, inspect /etc/apache2/sites-enabled/source.conf and verify that it contains the line SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ ssh app "grep 'SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2' /etc/apache2/sites-enabled/source.conf"
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
  • Install testssl.sh sudo apt install -y testssl.sh -t buster-backports in Tails, then run testssl.sh <onion url>. Confirm that only TLSv1.3 is provided; all earlier protos are not.
amnesia@amnesia:~/Persistent/securedrop$ testssl https://e336cukmz45e4ittiaa35gxjojz6467355tkssnpjbclv3omk2fmb6yd.onion/
amnesia@amnesia:~/Persistent/securedrop$ testssl 
[...]
 Testing protocols via sockets except NPN+ALPN 

 SSLv2      not offered (OK)
 SSLv3      not offered (OK)
 TLS 1      not offered
 TLS 1.1    not offered
 TLS 1.2    not offered
 TLS 1.3    offered (OK): final
 NPN/SPDY   not offered
 ALPN/HTTP2 http/1.1 (offered)
[...]
  • #5979 - Remove expired key
    • verify that the old signing key with fingerprint 22245C81E3BAEB4138B36061310F561200F4AD77 is not present on the application or monitor servers, e.g. by running the command for s in app mon; do ssh $s sudo apt-key list from the Admin workstation
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ for s in app mon; do ssh $s sudo apt-key list | grep -b2 -a1 "2224"; done
Warning: apt-key output should not be parsed (stdout is not a terminal)
Warning: apt-key output should not be parsed (stdout is not a terminal)
  • Verify that the new signing key 2359 E653 8C06 13E6 5295 5E6C 188E DD3B 7B22 E6A3 is present on the servers
amnesia@amnesia:~/Persistent/securedrop$ git branch --points-at HEAD
* release/2.1.0
amnesia@amnesia:~/Persistent/securedrop$ for s in app mon; do ssh $s sudo apt-key list | grep -b2 -a1 "2359"; done
Warning: apt-key output should not be parsed (stdout is not a terminal)
326-pub   rsa4096 2021-05-10 [SC] [expires: 2022-07-04]
378:      2359 E653 8C06 13E6 5295  5E6C 188E DD3B 7B22 E6A3
435-uid           [ unknown] SecureDrop Release Signing Key <[email protected]>
Warning: apt-key output should not be parsed (stdout is not a terminal)
326-pub   rsa4096 2021-05-10 [SC] [expires: 2022-07-04]
378:      2359 E653 8C06 13E6 5295  5E6C 188E DD3B 7B22 E6A3
435-uid           [ unknown] SecureDrop Release Signing Key <[email protected]>

@conorsch
Copy link
Contributor

No further concerns to point out. 100% of all testinfra tests pass on my prod VMs, which is mostly due to the key clobbering mentioned in #6103 (comment), but again, not a release-blocker. 2.1.0 is looking good to me! Will focus on LM tasks over the weekend to see if we can't drum up a bit more coverage. Thereafter, it's by the numbers.

@zenmonkeykstop
Copy link
Contributor

zenmonkeykstop commented Oct 15, 2021

So far so good on 2.1.0~rc2. One issue of note is that on the clean install scenario, I observed an apt-update failure:
...
It appears the order of operations is as follows:

1. All apt packages updated in `prepare-servers`, as expected

2. apt-test key is added via local modifications of the `install-fpf-repo` vars, as part of QA

3. apt-test repo URL is added via local modifications of the `install-fpf-repo` vars, as part of QA

4. the `securedrop-keyring` package is installed from apt-test, which clobbers the test key with the prod key upon installation

5. the `common` role fails to update apt lists again, since the apt-test repo is configured but the key is removed

This issue only affects QA testing, it isn't a problem for prod. But I'm documenting it here in case other testers encounter it.

I'm confused as to why we weren't seeing this all the time in QA, it looks like the only possibly-relevant change was the upgrade added in prepare-servers. It could probably be avoided by either:

  • installing the test key to a test keyring rather than using the same one as for the prod apt server, or
  • skipping the securedrop-keyring package install on non-prod runs (it's already being skipped in staging, adding an extra clause like when "apt_repo_url" != "https://apt.freedom.press" would seem like the minimal change necessary)

I don't think it merits another RC if there's a workaround, but we should fix this for the next release, as it means clean install test scenarios aren't exactly representative of reality.

@cfm
Copy link
Member

cfm commented Oct 15, 2021

I've updated #6103 (comment) to log some surprising testinfra failures in the VM upgrade scenario, which I'll investigate further on Monday.

@cfm
Copy link
Member

cfm commented Oct 18, 2021

#6103 (comment):

I've updated #6103 (comment) to log some surprising testinfra failures in the VM upgrade scenario, which I'll investigate further on Monday.

Initial investigations follow.


FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor-utils]
FAILED app/test_apparmor.py::test_apparmor_pkg[paramiko:/app-apparmor] - para...
FAILED app/test_apparmor.py::test_apparmor_apache_capabilities[paramiko:/app-dac_override]

paramiko.ssh_exception.SSHException: No existing session errors look like transient SSH failures.


FAILED app/test_ossec_agent.py::test_hosts_files[paramiko:/app] - AssertionEr...

Seems to be looking for default hostname mon rather than configured hostname mon-prod:

amnesia@amnesia:~/Persistent/securedrop$ ssh app cat /etc/hosts
127.0.0.1    localhost
127.0.1.1    app-prod    app-prod

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.121.58  mon-prod securedrop-monitor-server-alias

FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/app]
FAILED common/test_fpf_apt_repo.py::test_fpf_apt_repo_present[paramiko:/mon]

Expected per #6103 (comment).


FAILED common/test_automatic_updates.py::test_unattended_upgrades_functional[paramiko:/app]

Looks like a change in unattended-upgrades --dry-run behavior:

E       assert 'No packages found that can be upgraded unattended and no pending auto-removals' in "Starting unattended upgrades script\nAllowed origins are: origin=Ubuntu,archive=focal, origin=Ubuntu,archive=focal-se...ades installed\nInstCount=0 DelCount=0 BrokenCount=0\nThe list of kept packages can't be calculated in dry-run mode.\n"
E        +  where "Starting unattended upgrades script\nAllowed origins are: origin=Ubuntu,archive=focal, origin=Ubuntu,archive=focal-se...ades installed\nInstCount=0 DelCount=0 BrokenCount=0\nThe list of kept packages can't be calculated in dry-run mode.\n" = CommandResult(command=b'sudo unattended-upgrades --dry-run --debug', exit_status=0, stdout=b"Starting unattended upgra.../usr/bin/dpkg --force-confdef --force-confold --force-confdef --force-confold --status-fd 10 --configure --pending \n').stdout

FAILED mon/test_ossec_server.py::test_ossec_connectivity[paramiko:/mon] - Ass...
FAILED mon/test_ossec_server.py::test_hosts_files[paramiko:/mon] - AssertionE...
FAILED mon/test_postfix.py::test_postfix_generic_maps[paramiko:/mon] - Assert...

Looking for default hostnames app and mon rather than configured hostnames {app,mon}-prod (see #6127 (comment)).

@zenmonkeykstop
Copy link
Contributor

Thanks @cfm:

  • SSH-over-Tor can be flaky, so I'd agree with that assessment for the first 2 errors
  • the testinfra tests take their expected values from a bunch of variables in a yaml file, with some of said values (like server IPs) in prod runs being overridden from the install_files/ansible-base/group_vars/all/site-specific file created by ./securedrop-admin sdconfig. Looks like the server hostnames are not being overridden. So if they're not named according to the reccos in the docs I'd expect to see this error. Would be an easy fix to override them too methinks - check molecule/testinfra/conftest.py.
  • The unattended-upgrades error may just be bad luck - the test seems to assume that the system is up-to-date, which will not be true if new updates have been made available since installation or the overnight run.

It would be good to get a clean run if possible on the unattended-upgrades one (immediately after a non-dry-run one should be cool), but otherwise I think we're ok here.

@cfm
Copy link
Member

cfm commented Oct 18, 2021

@zenmonkeykstop in #6103 (comment):

  • The unattended-upgrades error may just be bad luck - the test seems to assume that the system is up-to-date, which will not be true if new updates have been made available since installation or the overnight run.

It would be good to get a clean run if possible on the unattended-upgrades one (immediately after a non-dry-run one should be cool), but otherwise I think we're ok here.

Thanks for this suggestion. Confirmed that common/test_automatic_updates.py::test_unattended_upgrades_functional has subsequently passed with:

amnesia@amnesia:~/Persistent/securedrop$ ssh app "sudo unattended-upgrades -d && sudo reboot"
amnesia@amnesia:~/Persistent/securedrop$ ssh mon "sudo unattended-upgrades -d && sudo reboot"
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin verify

So all of #6103 (comment) is safe to ignore for QA and release purposes.

@zenmonkeykstop
Copy link
Contributor

  • Install target: Nuc8+10
  • Tails version: 4.22
  • Test Scenario: upgrade
  • SSH over Tor: y
  • Release candidate: rc2
  • General notes:

Basic Server Testing

  • After installing the testinfra dependencies, all tests in ./securedrop-admin verify are passing: FAIL
    • Install dependencies on Admin Workstation with cd ~/Persistent/securedrop && ./securedrop-admin setup -t
    • Run tests with ./securedrop-admin verify (this will take a while)
    • Remove test dependencies: rm -rf admin/.venv3/ && ./securedrop-admin setup
  • QA Matrix checks pass with exception of above

Command Line User Generation

  • Can successfully add admin user and login

Administration

  • I have backed up and successfully restored the app server following the backup documentation skipped
  • If doing upgrade testing, make a backup on 2.0.2 and restore this backup on this release candidate skipped
  • "Send Test OSSEC Alert" button in the journalist triggers an OSSEC alert and an email is sent skipped
  • Can successfully add journalist account with HOTP authentication

Application Acceptance Testing

Source Interface SKIPPED

Journalist Interface SKIPPED

Login base cases
  • Journalist account with HOTP can log in

2.1.0 release-specific changes

Web Applications

  • #6075 - valid HTML time tags

    • Verify that the SI lookup page HTML validates correctly (e.g. by using a Firefox addon)
  • #5695 - scrypt and sessions refactor

    • Verify that no codename- or session-related errors were observed during application acceptance testing
    • On the Source Interface, create a new user, log out, return to the home page without clearing the Tor Browser identity, and repeat this process at least 5 times. Verify that you do not observe an error wwith the text "you have been logged out due to inactivity" while repeating these steps.
    • If possible, log in to the Source Interface simultaneously as separate users from multiple instances of the Tor Browser, submit messages from each user, and reply to the users from the Journalist Interface.
      • Verify that each user has a distinct codename in the SI and journalist designation in the JI
      • Verify that no errors were observed on the Source Interface in any user session
      • Verify that submitted messages appear on the correct source page on the JI
      • Verify that sources receive the correct reply in the SI and that the reply can be read
      • On the application server, verify that each source has a distinct GPG key fingerprint, e.g. with the command sudo -u www-data gpg --homedir /var/lib/securedrop/keys -k
    • (Upgrade-only) Verify that a source user created before the upgrade can still log in using their existing codename, that their journalist designation is unchanged, and that they can still read existing and new replies not tested as I nuked said old source :(
  • #6041 - SI a11y refactor

    • Verify that Source Interface functionality and appearance is unchanged
    • Using the Web Developer tools' Accessibility Inspector and check for issues: All, verify that accessibility issues have been significantly reduced on the SI /lookup page vs the corresponding page on https://demo-source.securedrop.org
    • Compare other pages on the SI in the same way - verify that issues have been removed and no new issues introduced.
  • #5958 - OTP secret length

    • verify that the TOTP secret and corresponding QRcode displayed via manage.py add-admin on the application server are 32 chars long and can be used to generate valid TOTP codes via the Google Authenticator mobile app
    • verify that the TOTP secret and QRCode generated via the JI during user creation are also 32 chars long and valid
    • (upgrade-only) verify that an existing 16-char shared secret can still be used to generate valid TOTP codes and log in
    • (upgrade-only) verify that when a user account with an existing 16-char shared secret has its OTP secret regenerated, the new OTP shared secret is 32 chars long and valid.
    • (HOTP-only) verify that an account using HOTP can still be created and used to log in.
  • #5696 - no JS in user delete modal

    • Set the Tor Browser security level to Safest
    • In the Journalist Interface, verify that a confirmation modal dialog is displayed when a user is selected for deletion
    • Choose Cancel and verify that the user was not deleted
    • Select the user for deletion again and choose Delete. Verify that the user was deleted.
  • #5988 - TLS 1.3 only

    • (HTTPS-only) On the Source Interface index page, verify via the Tor Browser Security Tab that the connection is encrypted not only as an Onion Service but also vi TLS 1.3 with 256-bit keys
    • (HTTPS-only) On the application server, inspect /etc/apache2/sites-enabled/source.conf and verify that it contains the line SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
  • #5979 - Remove expired key

    • verify that the old signing key with fingerprint 22245C81E3BAEB4138B36061310F561200F4AD77 is not present on the application or monitor servers, e.g. by running the command for s in app mon; do ssh $s sudo apt-key list from the Admin workstation
    • Verify that the new signing key 2359 E653 8C06 13E6 5295 5E6C 188E DD3B 7B22 E6A3 is presenton the servers
  • #6107 - Mute fwupd alerts

    • Verify that no OSSEC alerts related to fwupd were received
  • #5909 - Manually-transferred backup

    • Back up the instance using ./securedrop-admin backup
    • Copy the backup file to the application server using e.g. scp sd-backup.tar.gz app:/tmp/sd-backup-transfer.tar.gz
    • Verify that the command ./securedrop-admin restore --no-transfer sd-backup-transfer.tar.gz completes successfully, and that the local backup file is not transferred to the server during the playbook run.
  • #6110 - Repair Tails installer skipped

    • (Tails version 4.18 or lesser):
      • Verify that ./securedrop-admin tailsconfig completes successfully and the Tails OS Updater starts without displaying errors
      • Verify that Tails can be successfully updated to the latest version.
    • (Tails latest version):
      • Verify that the command ./securedrop-admin tailsconfig completes successfully without triggering the Tails OS Updater.
      • on the admin workstation, edit /etc/os-release to change the TAILS_VERSION_ID to a pre-4.19 version
      • Run touch /usr/local/etc/ssl/certs/tails.boum.org-CA.pem
      • Run `./securedrop-admin tailsconfig and verify that it completes successfully
      • Verify that the contents of this file were added to /usr/local/etc/ssl/certs/tails.boum.org-CA.pem

@eloquence
Copy link
Member Author

Updated an Admin Workstation on Tails 4.22 from SecureDrop 2.0.2 to SecureDrop 2.1.0 successfully using the graphical updater.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
epic Meta issue tracking child issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants