arista-eosplus/pyeapi

Certificate validation doesn't work

zloo opened this issue · 14 comments

zloo commented

I tried to make certificate validation work, but it seems it's not possible at the moment.

The validation is disabled by default, and if you want to enable it, you have to pass enforce_verification in kwargs to HttpsEapiConnection constructor.

This constructor is only ever called from client.py / make_connection(), which in turn is only ever called from this line:

connection = make_connection(transport, host=host, username=username,

As you can see, we never pass the enforce_verification to make_connection function.

We need to either pass down **kwargs, or at the very least the enforce_verification should it ever be set.

Hi @zloo,

I've added support for using valid certs into pyeapi via a new transport type 'https_certs'. The new transport class is 'HttpsEapiCertConnection' and is available in the develop branch. I've documented how to use the new transport and it's associated parameters here.

Below is an example config file and python usage:

key_file - full path to user private key
cert_file - full path to user cert file
ca_file - full path to CA cert that is expected to have authorized the server and user

**ca_file is optional and if provided will validate the server against the CA as well.

https_certs is the new transport type used for validating certs.

Example eapi.conf:

[connection:veos1]
host: 192.168.0.11
username: arista
password: arista
transport: http

[connection:veos2]
host: 192.168.0.10
transport: https_certs
key_file: /home/arista/pyeapi/pyeapi/user1.key
cert_file: /home/arista/pyeapi/pyeapi/user1.cert
ca_file: /home/arista/pyeapi/pyeapi/ca.cert

Example Python usage:

>>> import pyeapi
>>> loaded = pyeapi.load_config('/home/arista/pyeapi/pyeapi/test/fixtures/dut.conf')
>>> print loaded
None
>>>
>>> conf1 = pyeapi.config_for('veos1')
>>> conf2 = pyeapi.config_for('veos2')
>>>
>>> print conf1
{u'username': u'arista', u'host': u'192.168.0.11', u'password': u'arista', u'transport': u'http'}
>>>
>>> print conf2
{u'cert_file': u'/home/arista/pyeapi/pyeapi/user1.cert', u'host': u'192.168.0.10', u'ca_file': u'/home/arista/pyeapi/pyeapi/ca.cert', u'key_file': u'/home/arista/pyeapi/pyeapi/user1.key', u'transport': u'https_certs'}
>>>
>>>
>>> node1 = pyeapi.connect_to('veos1')
>>> node1.enable('show version')
[{'command': 'show version', 'result': {u'uptime': 26359.7, u'memTotal': 3977260, u'version': u'4.21.2F', u'internalVersion': u'4.21.2F-10430819.4212F', u'serialNumber': u'spine2', u'systemMacAddress': u'2c:c2:60:94:d7:6c', u'bootupTimestamp': 1559826247.0, u'memFree': 3172084, u'modelName': u'vEOS', u'architecture': u'i386', u'isIntlVersion': False, u'internalBuildId': u'7b26fbef-3d08-4910-bb95-df08faaa5b13', u'hardwareRevision': u''}, 'encoding': 'json'}]
>>>
>>>
>>> node2 = pyeapi.connect_to('veos2')
>>> node2.enable('show version')
[{'command': 'show version', 'result': {u'uptime': 26364.39, u'memTotal': 3977260, u'version': u'4.21.2F', u'internalVersion': u'4.21.2F-10430819.4212F', u'serialNumber': u'spine1', u'systemMacAddress': u'2c:c2:60:56:df:93', u'bootupTimestamp': 1559826244.0, u'memFree': 3171628, u'modelName': u'vEOS', u'architecture': u'i386', u'isIntlVersion': False, u'internalBuildId': u'7b26fbef-3d08-4910-bb95-df08faaa5b13', u'hardwareRevision': u''}, 'encoding': 'json'}]

Let me know if this satisfies your needs.

zloo commented

Hi @mharista, thank you for the reply.

The develop branch is solving a completely unrelated issue - client verification via CA.

The issue I'm having is that there is no way to force validation (or verification) of the certificate on Arista box, because you disable it by default and provide no option to enable it.

This means that anyone on the path between the pyeapi client and Arista box can do a MitM attack and steal user credentials.

I think I am misunderstanding something. The new transport class does do the certificate validation and requires valid certs on the switch and optionally a CA cert as well. This connection transport does not require a username or password.

zloo commented

I am using username and password for logging into the switch.

The https transport has a bug that it doesn't allow verification of the switch SSL certificate. There is a reference to a parameter called enforce_verification in the code, but it's never given to the underlying make_connection so it never validates the switches certificate.

If I understand your new connection type correctly, it allows me to verify the Switch certificate, BUT i need to use client certificates+key for authentication.

That is correct for the new connection type.

What type of certificate do you have on your switch, a self-signed cert or a CA signed cert? What are you planning to use to validate the switches cert?

zloo commented

I have a normal certificate issued by a public certificate authority. I want to validate it against the system CA list, but I don't mind if I have to specifically tell the system to use a specific file as the CA.

So you want to use username/password and potentially pass a ca_file to validate the servers certificate against? I'm not sure what the system CA list is. Have you tried this with the enforce_verification parameter by hacking it to be sent in your own pyeapi for your env? I'm curious if it works.

zloo commented

So you want to use username/password and potentially pass a ca_file to validate the servers certificate against? I'm not sure what the system CA list is. Have you tried this with the enforce_verification parameter by hacking it to be sent in your own pyeapi for your env? I'm curious if it works.

Yes, that's what I want. The system CA list depends on the underlying python library that pyeapi uses. python-requests uses its own CA set, others might fall back to the system /etc/ssl/certs/ca-certificates.crt.

I did not try to hack it, but I can try tomorrow.

Gotcha. In that case I have more confidence that simply fixing the enforce_verification parameter will work. Let me know how your test goes. I'll look into creating a dev environment here.

Hi @zloo

Were your tests with the hacked parameter and current cert setup successful?

zloo commented

hi @mharista
sorry about the delay; i completely forgot i should test it. i'll try to recall that tomorrow, thanks for the bump!

Hi @zloo

Did you ever have a chance to test the param in your cert environment?

mk-fg commented

Trying to also setup this authentication type, I seem to be getting this error:
Error connecting to the device at 10.0.0.99: Unauthorized. b'No authentication header found'

But also can't find any documentation on how to include username into configured/trusted client certificate so far.
(as presumably device have to know which account to authorize for the cert somehow?)

So if you get around to making the test-case for the API, would greatly appreciate if you might also include e.g. openssl command creating the client cert itself there.

EDIT: found client-side cert generation info on the api https port, didn't think to check there, but still would be nice if whole https_certs mode was documented and had an example in this client as well.
(wrt error above - needed to set ssl profile for "api http-commands", use /CN=<username> <privilege-level> in the client cert, set trust for it in the profile and pre-generate server cert there)

Hi @zloo, @mk-fg, with the recent fix #236, you can pass ssl context to pyeapi client connector. You create ssl context outside of pyeapi, where you can provide client side certificate as well as CA certificate and force checking of the server's TLS certificate.

The fix effectively obsoletes and deprecates enforce_verification parameter, I'll make a note of it in the release notes of the next release and will remove it in the release after the next one.

The fix is available in the develop branch and will become available in master's next release.

With this I'm closing this issue, let me know if any questions.