bzr paramiko ssh client asks for password even when the server doesn't accept passwords

Bug #375867 reported by Martin Pool
16
This bug affects 2 people
Affects Status Importance Assigned to Milestone
Bazaar
Fix Released
Medium
John A Meinel
Twisted
Invalid
Undecided
Unassigned
paramiko
Fix Released
Low
Robey Pointer

Bug Description

In bug 332019:

---
> bzr checkout lp:update-manager
Connected (version 2.0, client Twisted)
SSH <email address hidden> password:
Authentication type (password) not permitted.
bzr: ERROR: Connection error: Unable to authenticate to SSH host as <email address hidden> Bad authentication type (allowed_types=['publickey'])

The most confusing part is that is asks for a password even knowing that
launchpad.net policy requires public key, even when there is no any
public keys (with or without password) around.
---

Running 'ssh -v' shows that Launchpad advertises that it only accepts pubkey authentication. In this case openssh doesn't both asking for a password, but the twisted client in bzr apparently does. I don't know if that's a bug in twisted, or in the way bzr uses it.

Related branches

Revision history for this message
Martin Pool (mbp) wrote :

I don't know if this is really in Twisted and I haven't reported it upstream yet.

Changed in bzr:
importance: Undecided → Medium
status: New → Confirmed
Revision history for this message
Paul Swartz (paulswartz) wrote :

It's not a bug in Conch; the conch cmdline client handles this case correctly.

Revision history for this message
Thomas Herve (therve) wrote :

1) Does bzr use Twisted?

2) Can you reproduce the problem? I can't.

Revision history for this message
John A Meinel (jameinel) wrote :

I'm pretty sure that the line:
Connected (version 2.0, client Twisted)

Is generated by Paramiko informing you that it is connecting to a Twisted SSH server, *not* that it is connecting via a Twisted client locally.

So the bug probably either exists in Paramiko, or in our ParamikoSSHVendor, such that it doesn't realize whether passwords are even allowed. My guess is that either paramiko isn't giving us this information, or we are failing to ask paramiko what is supported.

Revision history for this message
John A Meinel (jameinel) wrote :

The code in question is bzrlib/transport/ssh.py _paramiko_auth() which does:
    if _use_ssh_agent:
        agent = paramiko.Agent()
        for key in agent.get_keys():
            trace.mutter('Trying SSH agent key %s'
                         % paramiko.util.hexify(key.get_fingerprint()))
            try:
                paramiko_transport.auth_publickey(username, key)
                return
            except paramiko.SSHException, e:
                pass

    # okay, try finding id_rsa or id_dss? (posix only)
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
        return
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
        return

    if password:
        try:
            paramiko_transport.auth_password(username, password)
            return
        except paramiko.SSHException, e:
            pass

    # give up and ask for a password
    password = auth.get_password('ssh', host, username, port=port)
    try:
        paramiko_transport.auth_password(username, password)
    except paramiko.SSHException, e:
        raise errors.ConnectionError(
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)

So if you have an agent, it first tries to auth with all available keys, then it tries the rsa key (if it exists) then the dsa key (same), and if it hasn't authenticated yet, it tries password auth.

I don't know where we would check to see what authentication types are supported, such that we could skip some of these checks.

It also hints that if you have an agent with a lot of keys for various hosts, it would likely slow down your connection as it tries all of them.

I believe there is a bug open about wanting to specify an exact key to use...

Changed in twisted:
status: New → Invalid
Glyph Lefkowitz (glyph)
summary: - bzr twisted ssh client asks for password even when the server doesn't
- accept passwords
+ bzr ssh client asks for password even when the server doesn't accept
+ passwords
John A Meinel (jameinel)
summary: - bzr ssh client asks for password even when the server doesn't accept
- passwords
+ bzr paramiko ssh client asks for password even when the server doesn't
+ accept passwords
Revision history for this message
John A Meinel (jameinel) wrote :

So I traced down into paramiko, and the only place I see the list of allowed authentication types being set is part of:
    def _parse_userauth_failure(self, m):
        authlist = m.get_list()
        partial = m.get_boolean()
        if partial:
            self.transport._log(INFO, 'Authentication continues...')
            self.transport._log(DEBUG, 'Methods: ' + str(authlist))
            self.transport.saved_exception = PartialAuthentication(authlist)
        elif self.auth_method not in authlist:
            self.transport._log(INFO, 'Authentication type (%s) not permitted.' % self.auth_method)
            self.transport._log(DEBUG, 'Allowed methods: ' + str(authlist))
            self.transport.saved_exception = BadAuthenticationType('Bad authentication type', authlist)
        else:
            self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)

Now it would seem that transport.auth_publickey() can raise BadAuthenticationType, which would give us this info. The problem being that it would only do so if 'publickey' was not an allowed type to start with.

Put another way... we only find out that 'password' authentication is not allowed once an authentication attempt has been made and it fails because that method is not allowed.

Now I'm guessing that ssh protocol itself has a way to request the supported authentication methods up front, given that 'ssh -v bazaar.launchpad.net' says:
debug1: Connecting to bazaar.launchpad.net [91.189.90.11] port 22.
debug1: Connection established.
debug1: identity file /home/jameinel/.ssh/identity type -1
...
debug1: SSH2_MSG_SERVICE_REQUEST sent
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey

However, looking at paramiko the only thing it seems to support (as a server) is MSG_SERVICE_REQUEST 'ssh-userauth' which returns MSG_SERVICE_ACCEPT 'ssh-userauth'.

Anyway, I don't know the ssh protocol well enough, but I don't see a way in *paramiko* to explicitly query the remote server for a list of allowed authentication protocols. So for now, we only find out after it fails that it really doesn't support that...

Revision history for this message
John A Meinel (jameinel) wrote :

I'm adding paramiko to this bug, because I don't see a way to get at the information we need.

Revision history for this message
Paul Swartz (paulswartz) wrote :

The way to find out is to request the 'none' authentication method. It returns an authentication failure containing the list of authentication methods that the server supports. See RFC 4252, section 4 and 5.1.

Revision history for this message
John A Meinel (jameinel) wrote :

So... good and bad. It turns out that we should be able to do:

try:
  t.auth_none(username)
except paramiko.BadAuthenticationType, e:
  supported_types = e.allowed_types

And that does, indeed say ['publickey']

The bad is that the default code for paramiko's auth handling has:
        else:
            self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)

Which means that on every connect you would see:
  Connected (version 2.0, client Twisted)
  Authentication type (none) not permitted.

I suppose if we know that, we could use something from logging to temporarily disable INFO level logging. Something like:

logger = logging.getLogger('bzr.paramiko')
old_level = logger.level
logger.setLevel(logging.CRITICAL)
try:
  t.auth_none(username)
except paramiko.BadAuthenticationType, e:
  supported_types = e.allowed_types
else:
  return # We seem to have succeeded at authenticating without a password or ssh key
logger.setLevel(old_level)

And then we have the supported types list, without printing out cruft.

We can chose how we want to treat it from there, but I think all of that can be nicely encapsulated into
bzrlib/transport/ssh.py _paramiko_auth()

Someone just needs to write up the final patch for it.

Revision history for this message
Robey Pointer (robey) wrote :

i will make that log level DEBUG -- it's only interesting in a debug way anyway, i think.

btw, the varying-auth logic was copied into ssh_client.py a few years ago (in _auth) and has probably changed a lot since the version in the bazaar codebase. it might be work checking out.

Changed in paramiko:
assignee: nobody → Robey Pointer (robey)
importance: Undecided → Low
milestone: none → 1.7.5
status: New → Confirmed
status: Confirmed → Fix Committed
Robey Pointer (robey)
Changed in paramiko:
status: Fix Committed → Fix Released
John A Meinel (jameinel)
Changed in bzr:
status: Confirmed → Fix Committed
assignee: nobody → John A Meinel (jameinel)
milestone: none → 1.18
Revision history for this message
John A Meinel (jameinel) wrote :

Landed as bzr.dev 4556

Changed in bzr:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

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