nondeterministic failure when no keyring backends are available

Bug #1864204 reported by Dan Streetman on 2020-02-21
16
This bug affects 2 people
Affects Status Importance Assigned to Milestone
Python Keyring
New
Unknown
launchpadlib
High
Dan Streetman
python-keyring (Ubuntu)
Medium
Dan Streetman
Eoan
Medium
Dan Streetman
Focal
Medium
Dan Streetman
python-launchpadlib (Ubuntu)
Medium
Dan Streetman
Eoan
Medium
Dan Streetman
Focal
Medium
Dan Streetman

Bug Description

[impact]

when no backends are available for python-keyring, it will sometimes silently ignore all requests to set or get credentials, and sometimes it will raise RuntimeError for all requests.

[test case]

when there are no backends, there are 2 built-in backends that will be the only ones available, the 'chainer' backend and the 'fail' backend. The 'fail' backend always has priority 0, while the 'chainer' backend has priority 10 if it contains any actual backends, and priority 0 if it is empty. So in the situtation where there are no actual backends, both 'chainer' and 'fail' will have priority 0. Since the 'backends' class stores its backend implementation instances in a python set(), which is unordered, it is non-deterministic which of those backends are used at any given time.

Since the behavior is non-deterministic, simply trying this multiple times may result in each behavior. Rebooting or logout/login can help 'switch' the behavior, although simply re-running the test multiple times should be enough to eventually test both backends.

>>> import keyring
>>> [keyring.backend.get_all_keyring()]

the above python code will show the current list of backends, and since they both have the same priority (in this situation) the first in the list will be used. If the list looks like:

[[<keyring.backends.chainer.ChainerBackend object at 0x7f1f7018f3d0>, <keyring.backends.fail.Keyring object at 0x7f1f6fff0820>]]

with 'ChainerBackend' first, all keyring functions will return silently with None, e.g.:

>>> keyring.set_password('test', 'test', 'test')
>>> keyring.get_password('test', 'test')
>>>

with 'fail.Keyring' first, all keyring functions will raise RuntimeError:

>>> import keyring
>>> [keyring.backend.get_all_keyring()]
[[<keyring.backends.fail.Keyring object at 0x7f3b6ade73d0>, <keyring.backends.chainer.ChainerBackend object at 0x7f3b6ac48820>]]
>>> keyring.set_password('test', 'test', 'test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/keyring/core.py", line 63, in set_password
    _keyring_backend.set_password(service_name, username, password)
  File "/usr/lib/python3/dist-packages/keyring/backends/fail.py", line 24, in get_password
    raise RuntimeError(msg)
RuntimeError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
>>> keyring.get_password('test', 'test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/keyring/core.py", line 57, in get_password
    return _keyring_backend.get_password(service_name, username)
  File "/usr/lib/python3/dist-packages/keyring/backends/fail.py", line 24, in get_password
    raise RuntimeError(msg)
RuntimeError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.

[regression potential]

And regressions would likely occur when no keyring backends, or only a single keyring backend, are/is availble, since this lowers the priority of the 'chainer' keyring backend, and the intention of this is to fix the existing problem when there is no keyring backend.

It should be noted that because the current behavior is non-deterministic, alternating between no error at all and raised RuntimeError, some existing users of python-keyring that occasionally do not see any error (but then also occasionally see RuntimeError raised) will now always see NoKeyringError raised (which does inherit from RuntimeError, to keep backwards compatibility for users that currently catch RuntimeError).

That is not actually a regression, since the behavior was non-deterministic; one way had to be chosen, and always raising error when there are no keyring backends is more appropriate than always silently failing when there are no keyring backends.

[scope]

This affects Eoan and later, including upstream.

This does not affect Bionic or earlier, as they do not have the commit that introduces the bug.

this was introduced upstream by commit 0114733e91f249246c8fec9e659cd7ba2388ea0d which was first included in version 16.1.0.

[other info]

this affects any application that uses the python-launchpadlib library, as that internally uses keyrings.

Opened upstream PR to at least make the behavior deterministic:
https://github.com/jaraco/keyring/pull/424

Also adds new 'NoKeyringError' so callers can catch that specific error instead of raising generic RuntimeError.

Related branches

Dan Streetman (ddstreet) on 2020-02-21
Changed in python-keyring (Ubuntu):
importance: Undecided → Medium
Changed in python-keyring:
status: Unknown → New
Dan Streetman (ddstreet) on 2020-02-21
description: updated
Changed in python-keyring (Ubuntu Eoan):
importance: Undecided → Medium
Dan Streetman (ddstreet) on 2020-02-21
Changed in python-launchpadlib (Ubuntu Eoan):
importance: Undecided → Medium
Changed in python-launchpadlib (Ubuntu Focal):
importance: Undecided → Medium
Dan Streetman (ddstreet) on 2020-02-22
description: updated
description: updated
Launchpad Janitor (janitor) wrote :

Status changed to 'Confirmed' because the bug affects multiple users.

Changed in python-keyring (Ubuntu Eoan):
status: New → Confirmed
Changed in python-keyring (Ubuntu):
status: New → Confirmed
Changed in python-launchpadlib (Ubuntu Eoan):
status: New → Confirmed
Changed in python-launchpadlib (Ubuntu):
status: New → Confirmed
Dan Streetman (ddstreet) wrote :

waiting on upstream python-keyring review before fixing this in Debian/Ubuntu:
https://github.com/jaraco/keyring/pull/424

Changed in python-keyring (Ubuntu Focal):
assignee: nobody → Dan Streetman (ddstreet)
Changed in python-keyring (Ubuntu Eoan):
assignee: nobody → Dan Streetman (ddstreet)
Changed in python-launchpadlib (Ubuntu Eoan):
assignee: nobody → Dan Streetman (ddstreet)
Changed in python-launchpadlib (Ubuntu Focal):
assignee: nobody → Dan Streetman (ddstreet)
status: Confirmed → In Progress
Changed in python-launchpadlib (Ubuntu Eoan):
status: Confirmed → In Progress
Changed in python-keyring (Ubuntu Focal):
status: Confirmed → In Progress
Changed in python-keyring (Ubuntu Eoan):
status: Confirmed → In Progress
Dan Streetman (ddstreet) on 2020-03-14
description: updated
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package python-keyring - 18.0.1-2ubuntu1

---------------
python-keyring (18.0.1-2ubuntu1) focal; urgency=medium

  * d/p/lp1864204/0001-correct-docstring-for-KeyringLocked-class.patch,
    d/p/lp1864204/0002-add-NoKeyringError-and-change-backends-fail-to-use-i.patch,
    d/p/lp1864204/0003-lower-empty-backends-chain-priority-to-10.-Ref-372.patch:
    - make failure deterministic when no keyring backends are available
      (LP: #1864204)

 -- Dan Streetman <email address hidden> Sat, 14 Mar 2020 07:45:55 -0400

Changed in python-keyring (Ubuntu Focal):
status: In Progress → Fix Released

Hello Dan, or anyone else affected,

Accepted python-keyring into eoan-proposed. The package will build now and be available at https://launchpad.net/ubuntu/+source/python-keyring/18.0.1-1ubuntu1 in a few hours, and then in the -proposed repository.

Please help us by testing this new package. See https://wiki.ubuntu.com/Testing/EnableProposed for documentation on how to enable and use -proposed. Your feedback will aid us getting this update out to other Ubuntu users.

If this package fixes the bug for you, please add a comment to this bug, mentioning the version of the package you tested and change the tag from verification-needed-eoan to verification-done-eoan. If it does not fix the bug for you, please add a comment stating that, and change the tag to verification-failed-eoan. In either case, without details of your testing we will not be able to proceed.

Further information regarding the verification process can be found at https://wiki.ubuntu.com/QATeam/PerformingSRUVerification . Thank you in advance for helping!

N.B. The updated package will be released to -updates after the bug(s) fixed by this package have been verified and the package has been in -proposed for a minimum of 7 days.

Changed in python-keyring (Ubuntu Eoan):
status: In Progress → Fix Committed
tags: added: verification-needed verification-needed-eoan

All autopkgtests for the newly accepted python-keyring (18.0.1-1ubuntu1) for eoan have finished running.
The following regressions have been reported in tests triggered by the package:

salt/2018.3.4+dfsg1-6ubuntu1 (i386)

Please visit the excuses page listed below and investigate the failures, proceeding afterwards as per the StableReleaseUpdates policy regarding autopkgtest regressions [1].

https://people.canonical.com/~ubuntu-archive/proposed-migration/eoan/update_excuses.html#python-keyring

[1] https://wiki.ubuntu.com/StableReleaseUpdates#Autopkgtest_Regressions

Thank you!

Dan Streetman (ddstreet) wrote :

eoan verification:

ubuntu@lp1864204-e:~$ dpkg -l |grep python3-keyring
ii python3-keyring 18.0.1-1 all store and access your passwords safely - Python 3 version of the package

ubuntu@lp1864204-e:~$ python3
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> [keyring.backend.get_all_keyring()]
[[<keyring.backends.chainer.ChainerBackend object at 0x7f1dbd2a3ed0>, <keyring.backends.fail.Keyring object at 0x7f1dbca5b510>]]
>>> keyring.set_password('test', 'test', 'test')
>>>

ubuntu@lp1864204-e:~$ python3
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> [keyring.backend.get_all_keyring()]
[[<keyring.backends.fail.Keyring object at 0x7f883c612cd0>, <keyring.backends.chainer.ChainerBackend object at 0x7f883bdca4d0>]]
>>> keyring.set_password('test', 'test', 'test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/keyring/core.py", line 63, in set_password
    _keyring_backend.set_password(service_name, username, password)
  File "/usr/lib/python3/dist-packages/keyring/backends/fail.py", line 24, in get_password
    raise RuntimeError(msg)
RuntimeError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.

ubuntu@lp1864204-e:~$ dpkg -l |grep python3-keyring
ii python3-keyring 18.0.1-1ubuntu1 all store and access your passwords safely - Python 3 version of the package
ubuntu@lp1864204-e:~$ python3
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> [keyring.backend.get_all_keyring()]
[[<keyring.backends.chainer.ChainerBackend object at 0x7f5ad686b450>, <keyring.backends.fail.Keyring object at 0x7f5ad600a5d0>]]
>>> keyring.set_password('test', 'test', 'test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/keyring/core.py", line 63, in set_password
    _keyring_backend.set_password(service_name, username, password)
  File "/usr/lib/python3/dist-packages/keyring/backends/fail.py", line 25, in get_password
    raise NoKeyringError(msg)
keyring.errors.NoKeyringError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.

tags: added: verification-done verification-done-eoan
removed: verification-needed verification-needed-eoan
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package python-keyring - 18.0.1-1ubuntu1

---------------
python-keyring (18.0.1-1ubuntu1) eoan; urgency=medium

  * d/p/lp1864204/0001-correct-docstring-for-KeyringLocked-class.patch,
    d/p/lp1864204/0002-add-NoKeyringError-and-change-backends-fail-to-use-i.patch,
    d/p/lp1864204/0003-lower-empty-backends-chain-priority-to-10.-Ref-372.patch:
    - make failure deterministic when no keyring backends are available
      (LP: #1864204)

 -- Dan Streetman <email address hidden> Sat, 14 Mar 2020 07:45:55 -0400

Changed in python-keyring (Ubuntu Eoan):
status: Fix Committed → Fix Released

The verification of the Stable Release Update for python-keyring has completed successfully and the package is now being released to -updates. Subsequently, the Ubuntu Stable Release Updates Team is being unsubscribed and will not receive messages about this bug report. In the event that you encounter a regression using the package from -updates please report a new bug using ubuntu-bug and tag the bug report regression-update so we can easily find any regressions.

Colin Watson (cjwatson) wrote :

Fixed in launchpadlib 1.10.11. Thanks for the contribution.

Changed in launchpadlib:
assignee: nobody → Dan Streetman (ddstreet)
milestone: none → 1.10.11
status: New → Fix Released
Colin Watson (cjwatson) on 2020-04-14
Changed in launchpadlib:
importance: Undecided → High
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package python-launchpadlib - 1.10.13-1

---------------
python-launchpadlib (1.10.13-1) unstable; urgency=medium

  * New upstream release.
    - Fix test runs under sudo.

 -- Colin Watson <email address hidden> Sun, 19 Apr 2020 10:35:39 +0100

Changed in python-launchpadlib (Ubuntu Focal):
status: In Progress → Fix Released
Brian Murray (brian-murray) wrote :

The Eoan Ermine has reached end of life, so this bug will not be fixed for that release

Changed in python-launchpadlib (Ubuntu Eoan):
status: In Progress → Won't Fix
To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Duplicates of this bug

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.