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

No cipher option #211

Closed
alarig opened this issue Sep 22, 2021 · 6 comments
Closed

No cipher option #211

alarig opened this issue Sep 22, 2021 · 6 comments

Comments

@alarig
Copy link

alarig commented Sep 22, 2021

EOS uses deprecated ciphers by default and we can’t specify the ciphers to use (with ssl.create_default_context().set_ciphers('DHE-RSA-AES256-SHA'), so it’s impossible to connect from a system removing deprecated ciphers by default:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/pyeapi/eapilib.py", line 440, in send
    self.transport.endheaders(message_body=data)
  File "/usr/lib/python3.9/http/client.py", line 1252, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1012, in _send_output
    self.send(msg)
  File "/usr/lib/python3.9/http/client.py", line 952, in send
    self.connect()
  File "/usr/lib/python3.9/http/client.py", line 1426, in connect
    self.sock = self._context.wrap_socket(self.sock,
  File "/usr/lib/python3.9/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.9/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.9/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1145)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/napalm/eos/eos.py", line 167, in open
    sh_ver = self.device.run_commands(["show version"])
  File "/usr/lib/python3.9/site-packages/napalm/eos/pyeapi_syntax_wrapper.py", line 42, in run_commands
    return super(Node, self).run_commands(commands, *args, **kwargs)
  File "/usr/lib/python3.9/site-packages/pyeapi/client.py", line 771, in run_commands
    response = self._connection.execute(commands, encoding, **kwargs)
  File "/usr/lib/python3.9/site-packages/pyeapi/eapilib.py", line 554, in execute
    response = self.send(request)
  File "/usr/lib/python3.9/site-packages/pyeapi/eapilib.py", line 483, in send
    raise ConnectionError(str(self), error_msg)
pyeapi.eapilib.ConnectionError: Socket error during eAPI connection: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1145)

Running a simple urllib.request.Request without the cipher option from ssl returns the same error, by specifying the ciphers it works.

~ % python
Python 3.9.6 (default, Sep 22 2021, 15:28:10) 
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> import urllib.request
>>> url = urllib.request.Request('https://edge-1/')
>>> ssl_context = ssl.create_default_context()
>>> ssl_context.set_ciphers('DHE-RSA-AES256-SHA, AES256-SHA')
>>> data = urllib.request.urlopen(url).read().decode()
Traceback (most recent call last):
  File "/usr/lib/python3.9/urllib/request.py", line 1346, in do_open
    h.request(req.get_method(), req.selector, req.data, headers,
  File "/usr/lib/python3.9/http/client.py", line 1257, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1303, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1252, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1012, in _send_output
    self.send(msg)
  File "/usr/lib/python3.9/http/client.py", line 952, in send
    self.connect()
  File "/usr/lib/python3.9/http/client.py", line 1426, in connect
    self.sock = self._context.wrap_socket(self.sock,
  File "/usr/lib/python3.9/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.9/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.9/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1145)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/urllib/request.py", line 214, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.9/urllib/request.py", line 517, in open
    response = self._open(req, data)
  File "/usr/lib/python3.9/urllib/request.py", line 534, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
  File "/usr/lib/python3.9/urllib/request.py", line 494, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.9/urllib/request.py", line 1389, in https_open
    return self.do_open(http.client.HTTPSConnection, req,
  File "/usr/lib/python3.9/urllib/request.py", line 1349, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1145)>
>>> data = urllib.request.urlopen(url, context=ssl_context).read().decode()
Traceback (most recent call last):
  File "/usr/lib/python3.9/urllib/request.py", line 1346, in do_open
    h.request(req.get_method(), req.selector, req.data, headers,
  File "/usr/lib/python3.9/http/client.py", line 1257, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1303, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1252, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.9/http/client.py", line 1012, in _send_output
    self.send(msg)
  File "/usr/lib/python3.9/http/client.py", line 952, in send
    self.connect()
  File "/usr/lib/python3.9/http/client.py", line 1426, in connect
    self.sock = self._context.wrap_socket(self.sock,
  File "/usr/lib/python3.9/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.9/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.9/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1145)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/urllib/request.py", line 214, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.9/urllib/request.py", line 517, in open
    response = self._open(req, data)
  File "/usr/lib/python3.9/urllib/request.py", line 534, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
  File "/usr/lib/python3.9/urllib/request.py", line 494, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.9/urllib/request.py", line 1389, in https_open
    return self.do_open(http.client.HTTPSConnection, req,
  File "/usr/lib/python3.9/urllib/request.py", line 1349, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1145)>
>>> 

Without a ssl context, urrlib can’t connect to the arista box at all, by specifing it, it’s only a matter of self signed certificate.

@u1735067
Copy link

Hello, I've hit this issue too (with Nautobot/NAPALM in a Python 3.10 Alpine container). Some ressources if that's useful:

As a workaround, it is possible to patch pyeapi to force the ciphers used:

def patch_pyeapi_ciphers():
    """
    Patch pyeapi to set ssl context ciphers, because Python set defaults that might be too high,
    see https://github.com/python/cpython/blob/3.10/Modules/_ssl.c#L158

    ```sh
    python -c 'import ssl; print(ssl._DEFAULT_CIPHERS)'
    @SECLEVEL=2:ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES:DHE+AES:!aNULL:!eNULL:!aDSS:!SHA1:!AESCCM
    ```
    """
    try:
        import pyeapi.eapilib
    except ImportError:
        return

    connect_orig = pyeapi.eapilib.HttpsConnection.connect

    def connect(self):
        self._context.set_ciphers('DEFAULT@SECLEVEL=2')
        return connect_orig(self)

    pyeapi.eapilib.HttpsConnection.connect = connect


patch_pyeapi_ciphers()

If you're using client certificate auth, another function is probably requiring to be patched.

To test:

import pyeapi
conn = pyeapi.connect(host='my_ip', transport='https')
conn.execute(['show version'])

@lodpp
Copy link

lodpp commented Feb 23, 2022

To let a trace somewhere, on the Arista side you can tweak the SSL/TLS profile to set proper TLS settings.
That way it avoids messing around with pyeos.

It might depends on the version you are running though but at least it's working on 4.25+

!
management security
   ssl profile MY_CUSTOM_PROFILE
      tls versions 1.2
      cipher-list ECDHE-RSA-AES256-GCM-SHA384 << Add your cipher list
      certificate capi.pem key capikey.pem
      trust certificate ARISTA_SIGNING_CA.crt
      trust certificate ARISTA_ROOT_CA.crt
!
management api http-commands
   protocol https ssl profile MY_CUSTOM_PROFILE
   no shutdown

capi.pem key and capikey.pem are the device certificate custom certificate stored in /persist/secure/ssl/certs/capi.pem /persist/secure/ssl/keys/capikey.pem

@bswinnerton
Copy link

capi.pem key and capikey.pem are the device certificate custom certificate stored in /persist/secure/ssl/certs/capi.pem /persist/secure/ssl/keys/capikey.pem

@lodpp These don't appear to exist by default. Was there something you had to do to generate them?

@RyanFalkenberg-OICR
Copy link

RyanFalkenberg-OICR commented May 30, 2022

@bswinnerton
capi.pem and capikey.pem aren't there by default on my devices either.

I generated them with these commands, choose a validity period and common-name that makes sense for you.

security pki key generate rsa 4096 capikey.pem
security pki certificate generate self-signed capi.pem key capikey.pem validity 3650 parameters common-name YOUR_SWITCH_HOSTNAME

@lodpp
Copy link

lodpp commented Jun 1, 2022

I like @RyanFalkenberg-OICR EOS method
Though you can use the plain openssl method as well ( from my template stuff to fast-bootstrap an arista)

! Generating and using new device certificate to use our custom SSL config
!
bash

cp /persist/secure/capikey.pem /persist/secure/ssl/keys/
HOST=`hostname`
openssl req -new -key /persist/secure/ssl/keys/capikey.pem -out /persist/secure/ssl/certs/capi.csr -subj "/C=FR/ST=blah/L=blah/O=blah/OU=blah/CN=$HOST/emailAddress=some@email.net"
openssl x509 -req -sha256 -days 3650 -in /persist/secure/ssl/certs/capi.csr -signkey /persist/secure/ssl/keys/capikey.pem -out /persist/secure/ssl/certs/capi.pem

echo "pay attention if error occurs"

exit

@dlyssenko
Copy link
Contributor

There is a way to specify a cypher for pyeapi, though it's undocumented one. I have covered it when resolved the issue #222. It's similar to what @u1735067 proposed in his pyeapi patch - specify the cypher as soon you get a client connection.
The issue is documented here: https://pyeapi.readthedocs.io/en/develop/client_modules/client.html#pyeapi.client.connect

Here's how one can set the cypher:

>>> cc = pyeapi.client.connect( transport='https', host=my_dut, username='admin')
>>> cc.transport._context.set_ciphers('DHE-RSA-AES256-SHA')
>>> c = pyeapi.client.Node( cc )
>>> c.enable('show version')
[{'command': 'show version', 'result': ...]
>>> 

Though providing a user-level option to specify the cypher it seems a reasonable request. I'll close this one and file an enhancement to provide a user option for cypher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants