Skip to content

Commit

Permalink
Id auth (#268)
Browse files Browse the repository at this point in the history
* Finish auth when identity auth succeeds
* Moved main authentication function to base class. Updated libssh client code. Updated identity auth test and added test for libssh client. Updated setup.py
* Added agent auth success test
* Updated changelog

Co-authored-by: Ivan Ivanou <[email protected]>
  • Loading branch information
pkittenis and iivanou authored Jan 4, 2021
1 parent 90a4a1f commit aab7caf
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 79 deletions.
8 changes: 8 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Change Log
============

2.5.1
+++++

Fixes
-----

* Successful identity file authentication would raise error - #264.

2.5.0
+++++

Expand Down
31 changes: 28 additions & 3 deletions pssh/clients/base/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from gevent.hub import Hub
from gevent.select import poll
from ssh2.utils import find_eol
from ssh2.exceptions import AgentConnectionError, AgentListIdentitiesError, \
AgentAuthenticationError, AgentGetIdentityError

from ..common import _validate_pkey_path
from ...constants import DEFAULT_RETRIES, RETRY_DELAY
Expand Down Expand Up @@ -307,8 +309,8 @@ def _identity_auth(self):
identity_file, ex)
continue
else:
logger.debug("Authentication succeeded with identity file %s",
identity_file)
logger.info("Authentication succeeded with identity file %s",
identity_file)
return
raise AuthenticationError("No authentication methods succeeded")

Expand All @@ -319,7 +321,30 @@ def _keepalive(self):
raise NotImplementedError

def auth(self):
raise NotImplementedError
if self.pkey is not None:
logger.debug(
"Proceeding with private key file authentication")
return self._pkey_auth(self.pkey, password=self.password)
if self.allow_agent:
try:
self._agent_auth()
except (AgentAuthenticationError, AgentConnectionError, AgentGetIdentityError,
AgentListIdentitiesError) as ex:
logger.debug("Agent auth failed with %s"
"continuing with other authentication methods", ex)
except Exception as ex:
logger.error("Agent auth failed with - %s", ex)
else:
logger.debug("Authentication with SSH Agent succeeded")
return
if self.identity_auth:
try:
return self._identity_auth()
except AuthenticationError:
if self.password is None:
raise
logger.debug("Private key auth failed, trying password")
self._password_auth()

def _password_auth(self):
raise NotImplementedError
Expand Down
29 changes: 1 addition & 28 deletions pssh/clients/native/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
from gevent.select import POLLIN, POLLOUT
from ssh2.error_codes import LIBSSH2_ERROR_EAGAIN
from ssh2.exceptions import SFTPHandleError, SFTPProtocolError, \
Timeout as SSH2Timeout, AgentConnectionError, AgentListIdentitiesError, \
AgentAuthenticationError, AgentGetIdentityError
Timeout as SSH2Timeout
from ssh2.session import Session, LIBSSH2_SESSION_BLOCK_INBOUND, LIBSSH2_SESSION_BLOCK_OUTBOUND
from ssh2.sftp import LIBSSH2_FXF_READ, LIBSSH2_FXF_CREAT, LIBSSH2_FXF_WRITE, \
LIBSSH2_FXF_TRUNC, LIBSSH2_SFTP_S_IRUSR, LIBSSH2_SFTP_S_IRGRP, \
Expand Down Expand Up @@ -218,32 +217,6 @@ def _keepalive(self):
def _agent_auth(self):
self.session.agent_auth(self.user)

def auth(self):
if self.pkey is not None:
logger.debug(
"Proceeding with private key file authentication")
return self._pkey_auth(self.pkey, password=self.password)
if self.allow_agent:
try:
self._agent_auth()
except (AgentAuthenticationError, AgentConnectionError, AgentGetIdentityError,
AgentListIdentitiesError) as ex:
logger.debug("Agent auth failed with %s"
"continuing with other authentication methods", ex)
except Exception as ex:
logger.error("Unknown error during agent authentication - %s", ex)
else:
logger.debug("Authentication with SSH Agent succeeded")
return
if self.identity_auth:
try:
self._identity_auth()
except AuthenticationError:
if self.password is None:
raise
logger.debug("Private key auth failed, trying password")
self._password_auth()

def _pkey_auth(self, pkey_file, password=None):
self.session.userauth_publickey_fromfile(
self.user,
Expand Down
30 changes: 3 additions & 27 deletions pssh/clients/ssh/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,38 +153,14 @@ def _init_session(self, retries=1):
raise ex

def auth(self):
if self.pkey is not None:
logger.debug(
"Proceeding with private key file authentication")
return self._pkey_auth(self.pkey, password=self.password)
if self.allow_agent:
try:
self.session.userauth_agent(self.user)
except Exception as ex:
logger.debug(
"Agent auth failed with %s, "
"continuing with other authentication methods",
ex)
else:
logger.debug(
"Authentication with SSH Agent succeeded.")
return
if self.gssapi_auth or (self.gssapi_server_identity or self.gssapi_client_identity):
try:
self.session.userauth_gssapi()
return self.session.userauth_gssapi()
except Exception as ex:
logger.error(
"GSSAPI authentication with server id %s and client id %s failed - %s",
self.gssapi_server_identity, self.gssapi_client_identity,
ex)
if self.identity_auth:
try:
self._identity_auth()
except AuthenticationError:
if self.password is None:
raise
logger.debug("Private key auth failed, trying password")
self._password_auth()
self.gssapi_server_identity, self.gssapi_client_identity, ex)
return super(SSHClient, self).auth()

def _password_auth(self):
if not self.password:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: C',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
Expand Down
4 changes: 3 additions & 1 deletion tests/native/base_ssh2_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def setUpClass(cls):
cls.user = pwd.getpwuid(os.geteuid()).pw_name
cls.client = SSHClient(cls.host, port=cls.port,
pkey=PKEY_FILENAME,
num_retries=1)
num_retries=1,
identity_auth=False,
)

@classmethod
def tearDownClass(cls):
Expand Down
47 changes: 35 additions & 12 deletions tests/native/test_single_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,17 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import unittest
import os
import time
import subprocess
import shutil
import tempfile
from hashlib import sha256
from datetime import datetime

from gevent import socket, sleep, spawn, Timeout as GTimeout
from gevent import sleep, spawn, Timeout as GTimeout

from pssh.clients.native import SSHClient
from ssh2.session import Session
from ssh2.channel import Channel
from ssh2.exceptions import SocketDisconnectError, BannerRecvError, SocketRecvError, \
AgentConnectionError, AgentListIdentitiesError, \
AgentAuthenticationError, AgentGetIdentityError, SFTPProtocolError
Expand All @@ -37,7 +34,6 @@
AuthenticationError

from .base_ssh2_case import SSH2TestCase
from ..embedded_server.openssh import OpenSSHServer


class SSH2ClientTest(SSH2TestCase):
Expand Down Expand Up @@ -175,21 +171,32 @@ def test_manual_auth(self):
client.session.handshake(client.sock)
self.assertRaises(AuthenticationException, client.auth)

def test_default_identities_auth(self):
def test_identity_auth(self):
class _SSHClient(SSHClient):
IDENTITIES = (self.user_key,)
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
num_retries=1,
allow_agent=False)
client.session.disconnect()
client.disconnect()
client.pkey = None
del client.session
del client.sock
client._connect(self.host, self.port)
client._init_session()
# Default identities auth only
self.assertRaises(AuthenticationException, client._identity_auth)
# Default auth
self.assertRaises(AuthenticationException, client.auth)
client.IDENTITIES = (self.user_key,)
# Default identities auth only should succeed
client._identity_auth()
client.disconnect()
client._connect(self.host, self.port)
client._init_session()
# Auth should succeed
self.assertIsNone(client.auth())
# Standard init with custom identities
client = _SSHClient(self.host, port=self.port,
num_retries=1,
allow_agent=False)
self.assertIsInstance(client, SSHClient)

def test_agent_auth_failure(self):
class UnknownError(Exception):
Expand All @@ -201,7 +208,8 @@ def _agent_auth_agent_err():
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
num_retries=1,
allow_agent=True)
allow_agent=True,
identity_auth=False)
client.session.disconnect()
client.pkey = None
client._connect(self.host, self.port)
Expand All @@ -210,6 +218,20 @@ def _agent_auth_agent_err():
client._agent_auth = _agent_auth_agent_err
self.assertRaises(AuthenticationError, client.auth)

def test_agent_auth_fake_success(self):
def _agent_auth():
return
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
num_retries=1,
allow_agent=True,
identity_auth=False)
client.session.disconnect()
client.pkey = None
client._connect(self.host, self.port)
client._agent_auth = _agent_auth
self.assertIsNone(client.auth())

def test_agent_fwd(self):
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
Expand Down Expand Up @@ -317,6 +339,7 @@ def __init__(self, host, port, num_retries):
super(SSHClient, self).__init__(
host, port=port, num_retries=2,
allow_agent=True)
self.IDENTITIES = set()

def _init_session(self):
self.session = Session()
Expand Down
4 changes: 3 additions & 1 deletion tests/ssh/base_ssh_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ def setUpClass(cls):
cls.user = USER
cls.client = SSHClient(cls.host, port=cls.port,
pkey=PKEY_FILENAME,
num_retries=1)
num_retries=1,
identity_auth=False,
)

@classmethod
def tearDownClass(cls):
Expand Down
26 changes: 20 additions & 6 deletions tests/ssh/test_single_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,35 @@ def test_stderr(self):
self.assertListEqual(expected, stderr)
self.assertEqual(len(output), 0)

def test_default_identities_auth(self):
def test_identity_auth(self):
class _SSHClient(SSHClient):
IDENTITIES = (self.user_key,)
client = SSHClient(self.host, port=self.port,
pkey=self.user_key,
num_retries=1,
timeout=1,
allow_agent=False)
client.session.disconnect()
client.disconnect()
client.pkey = None
del client.session
del client.sock
client._connect(self.host, self.port)
client._init_session()
# Default identities auth only
self.assertRaises(AuthenticationException, client._identity_auth)
# Default auth
self.assertRaises(AuthenticationException, client.auth)
client.IDENTITIES = (self.user_key,)
# Default identities auth only should succeed
client._identity_auth()
client.disconnect()
del client.session
del client.sock
client._connect(self.host, self.port)
client._init_session()
# Auth should succeed
self.assertIsNone(client.auth())
# Standard init with custom identities
client = _SSHClient(self.host, port=self.port,
num_retries=1,
allow_agent=False)
self.assertIsInstance(client, SSHClient)

def test_long_running_cmd(self):
host_out = self.client.run_command('sleep 2; exit 2')
Expand Down

0 comments on commit aab7caf

Please sign in to comment.