Skip to content

Commit d973500

Browse files
authored
Cert (#236)
* Added certificate authentication for ssh client * Updated parallel and single clients for cert auth. Added embedded openssh certificate authentication support. * Added parallel client cert auth test. * Updated docs. * Updated requirements, setup.py * Updated changelog
1 parent ccdbc59 commit d973500

20 files changed

+250
-12
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ pypy
4444

4545
# Documentation builds
4646
doc/_build
47+
48+
tests/unit_test_cert_key-cert.pub
49+
tests/embedded_server/principals

Changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Change Log
22
============
33

4+
2.1.0
5+
+++++
6+
7+
Changes
8+
-------
9+
10+
* Added certificate authentication support for the ``pssh.clients.ssh`` clients.
11+
412
2.0.0
513
+++++
614

doc/advanced.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ GSS authentication allows logins using Windows LDAP configured user accounts via
9999
``ssh-python`` :py:class:`ParallelSSHClient <pssh.clients.ssh.parallel.ParallelSSHClient>` only.
100100

101101

102+
Certificate authentication
103+
--------------------------
104+
105+
In the ``pssh.clients.ssh`` clients, certificate authentication is supported.
106+
107+
.. code-block:: python
108+
109+
from pssh.clients.ssh import ParallelSSHClient
110+
111+
client = ParallelSSHClient(hosts, pkey='id_rsa', cert_file='id_rsa-cert.pub')
112+
113+
114+
Where ``id_rsa-cert.pub`` is an RSA signed certificate file for the ``id_rsa`` private key.
115+
116+
Both private key and corresponding signed public certificate file must be provided.
117+
118+
``ssh-python`` :py:mod:`ParallelSSHClient <pssh.clients.ssh>` clients only.
119+
120+
102121
Tunnelling
103122
**********
104123

doc/constants.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Constants
2+
==========
3+
4+
.. automodule:: pssh.constants
5+
:members:
6+
:undoc-members:
7+
:member-order: groupwise

pssh/clients/ssh/parallel.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class ParallelSSHClient(BaseParallelSSHClient):
3030
"""ssh-python based parallel client."""
3131

3232
def __init__(self, hosts, user=None, password=None, port=22, pkey=None,
33+
cert_file=None,
3334
num_retries=DEFAULT_RETRIES, timeout=None, pool_size=100,
3435
allow_agent=True, host_config=None, retry_delay=RETRY_DELAY,
3536
forward_ssh_agent=False,
@@ -52,6 +53,13 @@ def __init__(self, hosts, user=None, password=None, port=22, pkey=None,
5253
:param pkey: Private key file path to use. Path must be either absolute
5354
path or relative to user home directory like ``~/<path>``.
5455
:type pkey: str
56+
:param cert_file: Public key signed certificate file to use for
57+
authentication. The corresponding private key must also be provided
58+
via ``pkey`` parameter.
59+
For example ``pkey='id_rsa',cert_file='id_rsa-cert.pub'`` for RSA
60+
signed certificate.
61+
Path must be absolute or relative to user home directory.
62+
:type cert_file: str
5563
:param num_retries: (Optional) Number of connection and authentication
5664
attempts before the client gives up. Defaults to 3.
5765
:type num_retries: int
@@ -126,6 +134,7 @@ def __init__(self, hosts, user=None, password=None, port=22, pkey=None,
126134
host_config=host_config, retry_delay=retry_delay,
127135
identity_auth=identity_auth)
128136
self.pkey = _validate_pkey_path(pkey)
137+
self.cert_file = _validate_pkey_path(cert_file)
129138
self.forward_ssh_agent = forward_ssh_agent
130139
self.gssapi_auth = gssapi_auth
131140
self.gssapi_server_identity = gssapi_server_identity
@@ -243,7 +252,9 @@ def _make_ssh_client(self, host_i, host):
243252
host_i, host)
244253
_client = SSHClient(
245254
host, user=_user, password=_password, port=_port,
246-
pkey=_pkey, num_retries=self.num_retries,
255+
pkey=_pkey,
256+
cert_file=self.cert_file,
257+
num_retries=self.num_retries,
247258
timeout=self.timeout,
248259
allow_agent=self.allow_agent, retry_delay=self.retry_delay,
249260
gssapi_auth=self.gssapi_auth,

pssh/clients/ssh/single.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
from gevent.select import POLLIN, POLLOUT
2626
from ssh import options
2727
from ssh.session import Session, SSH_READ_PENDING, SSH_WRITE_PENDING
28-
from ssh.key import import_privkey_file
28+
from ssh.key import import_privkey_file, import_cert_file, copy_cert_to_privkey
2929
from ssh.exceptions import EOF
3030
from ssh.error_codes import SSH_AGAIN
3131

3232
from ..base.single import BaseSSHClient
3333
from ...exceptions import AuthenticationError, SessionError, Timeout
3434
from ...constants import DEFAULT_RETRIES, RETRY_DELAY
35+
from ..common import _validate_pkey_path
3536

3637

3738
logger = logging.getLogger(__name__)
@@ -43,6 +44,7 @@ class SSHClient(BaseSSHClient):
4344
def __init__(self, host,
4445
user=None, password=None, port=None,
4546
pkey=None,
47+
cert_file=None,
4648
num_retries=DEFAULT_RETRIES,
4749
retry_delay=RETRY_DELAY,
4850
allow_agent=True, timeout=None,
@@ -64,6 +66,13 @@ def __init__(self, host,
6466
be either absolute path or relative to user home directory
6567
like ``~/<path>``.
6668
:type pkey: str
69+
:param cert_file: Public key signed certificate file to use for
70+
authentication. The corresponding private key must also be provided
71+
via ``pkey`` parameter.
72+
For example ``pkey='id_rsa',cert_file='id_rsa-cert.pub'`` for RSA
73+
signed certificate.
74+
Path must be absolute or relative to user home directory.
75+
:type cert_file: str
6776
:param num_retries: (Optional) Number of connection and authentication
6877
attempts before the client gives up. Defaults to 3.
6978
:type num_retries: int
@@ -96,6 +105,7 @@ def __init__(self, host,
96105
:raises: :py:class:`pssh.exceptions.PKeyFileError` on errors finding
97106
provided private key.
98107
"""
108+
self.cert_file = _validate_pkey_path(cert_file, host)
99109
self.gssapi_auth = gssapi_auth
100110
self.gssapi_server_identity = gssapi_server_identity
101111
self.gssapi_client_identity = gssapi_client_identity
@@ -194,10 +204,18 @@ def _password_auth(self):
194204
raise AuthenticationError("Password authentication failed - %s", ex)
195205

196206
def _pkey_auth(self, pkey, password=None):
197-
password = b'' if not password else password
198207
pkey = import_privkey_file(pkey, passphrase=password)
208+
if self.cert_file is not None:
209+
logger.debug("Certificate file set - trying certificate authentication")
210+
self._import_cert_file(pkey)
199211
self.session.userauth_publickey(pkey)
200212

213+
def _import_cert_file(self, pkey):
214+
cert_key = import_cert_file(self.cert_file)
215+
self.session.userauth_try_publickey(cert_key)
216+
copy_cert_to_privkey(cert_key, pkey)
217+
logger.debug("Imported certificate file %s for pkey %s", self.cert_file, self.pkey)
218+
201219
def open_session(self):
202220
"""Open new channel from session."""
203221
logger.debug("Opening new channel on %s", self.host)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
gevent>=1.1
22
ssh2-python>=0.22.0
3-
ssh-python>=0.7.0
3+
ssh-python>=0.8.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
'*.tests', '*.tests.*')
3838
),
3939
install_requires=[
40-
'gevent>=1.1', 'ssh2-python>=0.22.0', 'ssh-python>=0.7.0'],
40+
'gevent>=1.1', 'ssh2-python>=0.22.0', 'ssh-python>=0.8.0'],
4141
classifiers=[
4242
'Development Status :: 5 - Production/Stable',
4343
'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)',

tests/embedded_server/ca_host_key

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
39+
niIHADTER1prOuXylm1pWZdk
40+
-----END PRIVATE KEY-----
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgu84sipb+xoLb4xY/PnjXE6HGJlJKz+VdqW+Z9oMFMv4AAAADAQABAAABgQCphJViWy6xwUQngUeHU2AmHaHj/kkDSI4dHQLoWhYwEQ85CowwDaJ7NhZBr6bpA0OTYBgXcfkQw8ncH2k6HybfeT9KXLogTS/hKwZIz1NY5bvVBHSXI8uzSX5OlBujPUxk99kc1Vm3OA9fKB0iCdufgmdXo+3c3JBG5EqO4H21u8zxwfHmVpigkERGxMYkOMWEjAF7i7kptG9d0Gtijw8GWYUmO7R2xkBWsLaZY3llOdCCH6EDYerfHdUOnkwvm0yhy4UqSt8Cu6XoErhdH4l+uF4Pbz+rWgmQ7GQlpSCgZqrIAyyQyTQOn+heFkGr407lFl+PkClXHK5NgZ90EgXSA+dRNl9Qjw3BO5tUN9jWALEURfT4CQemtSe+1JNMbxsA7+byW1YeLbVZknBMieRhMoerXRvG9ENglkYeFzKeKKAj0pS5FzzegXP4QxK+XAkX1Mw3GTRGkrpwrd8b6FVWf/yFzCFkm//rCQiQ933om57Q3QQriJtABfBH0L9bg2sAAAAAAAAAAAAAAAIAAAAMdGVzdF9jYV9ob3N0AAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQCphJViWy6xwUQngUeHU2AmHaHj/kkDSI4dHQLoWhYwEQ85CowwDaJ7NhZBr6bpA0OTYBgXcfkQw8ncH2k6HybfeT9KXLogTS/hKwZIz1NY5bvVBHSXI8uzSX5OlBujPUxk99kc1Vm3OA9fKB0iCdufgmdXo+3c3JBG5EqO4H21u8zxwfHmVpigkERGxMYkOMWEjAF7i7kptG9d0Gtijw8GWYUmO7R2xkBWsLaZY3llOdCCH6EDYerfHdUOnkwvm0yhy4UqSt8Cu6XoErhdH4l+uF4Pbz+rWgmQ7GQlpSCgZqrIAyyQyTQOn+heFkGr407lFl+PkClXHK5NgZ90EgXSA+dRNl9Qjw3BO5tUN9jWALEURfT4CQemtSe+1JNMbxsA7+byW1YeLbVZknBMieRhMoerXRvG9ENglkYeFzKeKKAj0pS5FzzegXP4QxK+XAkX1Mw3GTRGkrpwrd8b6FVWf/yFzCFkm//rCQiQ933om57Q3QQriJtABfBH0L9bg2sAAAGUAAAADHJzYS1zaGEyLTI1NgAAAYBv+fXgU7e+0OW/mBZbG6jlMtEALbF2IxjRe2KbmRMb5V7XVqVHVt1qAsl+aNtGntARVRMLnOn8Nasoquy3N+7voaMrQAADpAx+Ig3o8cNR1Ym86TUX7cuUTaSkWm7gOrfUILzIwJnyTJAV74NUac1LGXHL1w3EiLtkga5I+dsAm7Ba+8XohkK350D2lwjq8msvPsrPdT5IHoTG6XWO4QCJBYiu4FTV8Xye2XjFP60pH0F3RkrIFk9l3ftyJPm/2ptWgWJOM9TJimTcSy3bnt6DjLfUZJmePIXyyETEhBGQkRXyZ6VrJHd+biFdEz1X87BEVQcYoBBL5glnWgPAqsKzI70Z9pmS9SX7qA795Xp6zMJq0Ov/H7mUvZSvhnIip6NF9z4KOAy7mmF9dZR8UABE4upZpT2k0xIO+x+aJChkg8EwYX3z4v4VC+dzCIfUGEIfbrvYnQOh/VvG6A04lnALaJ/iRleXjmrbW2EJCDlOtZMTHqmvc2KLzponpJHyyeM= zefrer@kirin

tests/embedded_server/ca_host_key.pub

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCphJViWy6xwUQngUeHU2AmHaHj/kkDSI4dHQLoWhYwEQ85CowwDaJ7NhZBr6bpA0OTYBgXcfkQw8ncH2k6HybfeT9KXLogTS/hKwZIz1NY5bvVBHSXI8uzSX5OlBujPUxk99kc1Vm3OA9fKB0iCdufgmdXo+3c3JBG5EqO4H21u8zxwfHmVpigkERGxMYkOMWEjAF7i7kptG9d0Gtijw8GWYUmO7R2xkBWsLaZY3llOdCCH6EDYerfHdUOnkwvm0yhy4UqSt8Cu6XoErhdH4l+uF4Pbz+rWgmQ7GQlpSCgZqrIAyyQyTQOn+heFkGr407lFl+PkClXHK5NgZ90EgXSA+dRNl9Qjw3BO5tUN9jWALEURfT4CQemtSe+1JNMbxsA7+byW1YeLbVZknBMieRhMoerXRvG9ENglkYeFzKeKKAj0pS5FzzegXP4QxK+XAkX1Mw3GTRGkrpwrd8b6FVWf/yFzCFkm//rCQiQ933om57Q3QQriJtABfBH0L9bg2s= zefrer@kirin

tests/embedded_server/ca_user_key

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
39+
vP43QJnRZlNs4a4d41ifvA==
40+
-----END PRIVATE KEY-----

tests/embedded_server/ca_user_key.pub

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClYv7851Sl96qxb2VV8rbhqhpSmLD+VUvKjj4aMjFgunw8HA9BOUu5kuPLo7LWzZN1brTv0JB9xbcCvmNdzhHKEBsP4F3xp20vCz2EKivjGIJkFiPyn/xAZvlfA0rvoXyzwCekiFJqhrEsSpiHDk4jL9CImStcOhm/ZLXTjZL2aa+A2DSE4JFAXvpvuTbw68JSujCPWsNNyqydQNXpWkHoqI/Lo/dtXX1jYoD4zmwEqN++VwNIdANazQpABBkcVQfwVtcHvYptA9Pq4HhrTPA59Pv63yPb7/fL7F2CfKCW+sb+yRGY6U81vXVG+jRMhrcPTY6SIXqW43P0qhGW6NWswn1k5sLvpYHdRTyDdC1uwBVVQJ9kO/QP4SnXklfMVzuFIxt5BhM4dXwlPDuJcJG1rXUfXIAWtZJd/LCcyp+xKsJxgjTIitDevNRxzfkRkijryzUgfZHh27557ztoubl3NuLKf+cWcdLzBkeGpNNHCE+tqVueuBqiUqPttE6LSx0= zefrer@kirin

tests/embedded_server/openssh.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import random
2121
import string
2222
import logging
23+
import pwd
2324
from threading import Thread
2425
from subprocess import Popen
2526
from time import sleep
@@ -36,9 +37,12 @@
3637
PDIR_NAME = os.path.dirname(DIR_NAME)
3738
PPDIR_NAME = os.path.dirname(PDIR_NAME)
3839
SERVER_KEY = os.path.abspath(os.path.sep.join([DIR_NAME, 'rsa.key']))
40+
CA_HOST_KEY = os.path.abspath(os.path.sep.join([DIR_NAME, 'ca_host_key']))
3941
SSHD_CONFIG_TMPL = os.path.abspath(os.path.sep.join(
4042
[DIR_NAME, 'sshd_config.tmpl']))
4143
SSHD_CONFIG = os.path.abspath(os.path.sep.join([DIR_NAME, 'sshd_config']))
44+
PRINCIPALS_TMPL = os.path.abspath(os.path.sep.join([DIR_NAME, 'principals.tmpl']))
45+
PRINCIPALS = os.path.abspath(os.path.sep.join([DIR_NAME, 'principals']))
4246

4347

4448
class OpenSSHServer(object):
@@ -56,11 +60,13 @@ def __init__(self, listen_ip='127.0.0.1', port=2222):
5660
def _fix_masks(self):
5761
_mask = int('0600') if version_info <= (2,) else 0o600
5862
dir_mask = int('0755') if version_info <= (2,) else 0o755
59-
os.chmod(SERVER_KEY, _mask)
63+
for _file in [SERVER_KEY, CA_HOST_KEY]:
64+
os.chmod(_file, _mask)
6065
for _dir in [DIR_NAME, PDIR_NAME, PPDIR_NAME]:
6166
os.chmod(_dir, dir_mask)
6267

6368
def make_config(self):
69+
user = pwd.getpwuid(os.geteuid()).pw_name
6470
with open(SSHD_CONFIG_TMPL) as fh:
6571
tmpl = fh.read()
6672
template = Template(tmpl)
@@ -70,6 +76,12 @@ def make_config(self):
7076
random_server=self.random_server,
7177
))
7278
fh.write(os.linesep)
79+
with open(PRINCIPALS_TMPL) as fh:
80+
_princ_tmpl = fh.read()
81+
princ_tmpl = Template(_princ_tmpl)
82+
with open(PRINCIPALS, 'w') as fh:
83+
fh.write(princ_tmpl.render(user=user))
84+
fh.write(os.linesep)
7385

7486
def start_server(self):
7587
cmd = ['/usr/sbin/sshd', '-D', '-p', str(self.port),

tests/embedded_server/principals.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{user}}

tests/embedded_server/sshd_config.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ UsePAM no
33
HostbasedAuthentication no
44
IgnoreUserKnownHosts yes
55
ListenAddress {{listen_ip}}
6+
HostKey {{parent_dir}}/ca_host_key
7+
HostCertificate {{parent_dir}}/ca_host_key-cert.pub
8+
TrustedUserCAKeys {{parent_dir}}/ca_user_key.pub
9+
AuthorizedPrincipalsFile {{parent_dir}}/principals
610

711
AcceptEnv LANG LC_*
812
Subsystem sftp internal-sftp

tests/ssh/base_ssh_case.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import pwd
2020
import os
2121
import logging
22+
import subprocess
2223
from sys import version_info
2324

2425
from ..embedded_server.openssh import OpenSSHServer
@@ -39,14 +40,28 @@ def setup_root_logger():
3940

4041
PKEY_FILENAME = os.path.sep.join([os.path.dirname(__file__), '..', 'client_pkey'])
4142
PUB_FILE = "%s.pub" % (PKEY_FILENAME,)
43+
USER_CERT_PRIV_KEY = os.path.sep.join([os.path.dirname(__file__), '..', 'unit_test_cert_key'])
44+
USER_CERT_PUB_KEY = "%s.pub" % (USER_CERT_PRIV_KEY,)
45+
USER_CERT_FILE = "%s-cert.pub" % (USER_CERT_PRIV_KEY,)
46+
CA_USER_KEY = os.path.sep.join([os.path.dirname(__file__), '..', 'embedded_server', 'ca_user_key'])
47+
USER = pwd.getpwuid(os.geteuid()).pw_name
48+
49+
50+
def sign_cert():
51+
cmd = [
52+
'ssh-keygen', '-s', CA_USER_KEY, '-n', USER, '-I', 'tests', USER_CERT_PUB_KEY,
53+
]
54+
subprocess.check_call(cmd)
4255

4356

4457
class SSHTestCase(unittest.TestCase):
4558

4659
@classmethod
4760
def setUpClass(cls):
4861
_mask = int('0600') if version_info <= (2,) else 0o600
49-
os.chmod(PKEY_FILENAME, _mask)
62+
for _file in [PKEY_FILENAME, USER_CERT_PRIV_KEY, CA_USER_KEY]:
63+
os.chmod(_file, _mask)
64+
sign_cert()
5065
cls.host = '127.0.0.1'
5166
cls.port = 2322
5267
cls.server = OpenSSHServer(listen_ip=cls.host, port=cls.port)
@@ -55,7 +70,9 @@ def setUpClass(cls):
5570
cls.resp = u'me'
5671
cls.user_key = PKEY_FILENAME
5772
cls.user_pub_key = PUB_FILE
58-
cls.user = pwd.getpwuid(os.geteuid()).pw_name
73+
cls.cert_pkey = USER_CERT_PRIV_KEY
74+
cls.cert_file = USER_CERT_FILE
75+
cls.user = USER
5976
cls.client = SSHClient(cls.host, port=cls.port,
6077
pkey=PKEY_FILENAME,
6178
num_retries=1)

0 commit comments

Comments
 (0)