Skip to content

Commit

Permalink
Merge pull request #136 from napalm-automation/pytest
Browse files Browse the repository at this point in the history
Pytest
  • Loading branch information
mirceaulinic committed Jul 28, 2017
2 parents fa72546 + cd31fc2 commit a3faa02
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 42 deletions.
13 changes: 6 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ python:
- 2.7
- 3.4
- 3.5
before_install:
- pip install -r requirements-dev.txt
install:
- pip install .
- pip install tox-travis
deploy:
provider: pypi
user: mirucha
password:
secure: "uSMbPGF+iSjhNALEse35mNFDP34kwHwDTA8QZefSaDMlHy/SphPbcmwp4BQpY/cgrP83kYDHssNsjexNVU4lqZZ4PbNU3LB/O+UHL4Vd/RgehC9MceRDmzTUXQ1V5nwRo+KO2K4u9mp6xv1HRtx+3auF7PIBYk5T/C3jb/kztjnbVIFVtcX9YB01xXftm3qmDi1JEmXqgiFuhMD7AGZjVU9y166EHT6TlHvWeOQUwN8wTtwd0NqRoeTTGEXUK0o1HI3fVxXH5bGaVHk+xHuA6tUr8PLJHPV1rS39lwolfpfKWPX9OMmo43r7E8gZ37jUMWHRnPj/iBoJq344s9gtmyN8QEEfC9QCbE7X4vafccisLVI+F2c6leXTn1WrVLfJNp7opDhf2d6Q8n0uHP8/xL6bW+p1XZNT0qF0U57xRMaxpPqFM8sZvck8zKT4IFYS3mdUiSCZGXC3lakcuaO+ASquMHH9DY/2FSU9X0+t3tBrSQB53AUCSOblR3QIpOkinpJoPAPc9hmg+23rz4HxLAC4zkoOmZdpoZfRrP1V9TdYWMfnvSzrv/YoskzaQP0sZuAzkj7b/FelyCJ9BuYWLCF3jqQfSjeKlyx3C2Gf66GPWOspxWCR6MB6KN630k22YzDJqqZM1zxetxUjq/Lyvs0BQqeWbH9aHRJFd4l7iBs="
on:
tags: true
branch: master
script:
- cp examples/server.crt /tmp/__napalm_logs.crt
- cp examples/server.key /tmp/__napalm_logs.key
# - cd napalm_logs/scripts/
# - sudo python cli.py --log-file cli -l debug --certificate /tmp/__napalm_logs.crt --keyfile /tmp/__napalm_logs.key
- tox
after_success:
- coveralls
28 changes: 26 additions & 2 deletions napalm_logs/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
import threading

# Import napalm-logs pkgs
from napalm_logs.config import AUTH_ADDRESS
from napalm_logs.config import AUTH_PORT
from napalm_logs.config import MAGIC_REQ
from napalm_logs.config import MAGIC_ACK
from napalm_logs.proc import NapalmLogsProc
from napalm_logs.config import AUTH_MAX_CONN
from napalm_logs.config import AUTH_KEEP_ALIVE
from napalm_logs.config import AUTH_KEEP_ALIVE_ACK
from napalm_logs.exceptions import BindException
from napalm_logs.exceptions import SSLMismatchException
# exceptions
from napalm_logs.exceptions import NapalmLogsExit
Expand Down Expand Up @@ -56,12 +59,14 @@ def __init__(self,
keyfile,
private_key,
signature_hex,
skt):
auth_address=AUTH_ADDRESS,
auth_port=AUTH_PORT):
self.certificate = certificate
self.keyfile = keyfile
self.__key = private_key
self.__sgn = signature_hex
self.socket = skt
self.auth_address = auth_address
self.auth_port = auth_port
self.__up = False

def _exit_gracefully(self, signum, _):
Expand Down Expand Up @@ -115,6 +120,8 @@ def verify_cert(self):
'''
Checks that the provided cert and key are valid and usable
'''
log.debug('Verifying the %s certificate, keyfile: %s',
self.certificate, self.keyfile)
try:
ssl.create_default_context().load_cert_chain(self.certificate, keyfile=self.keyfile)
except ssl.SSLError:
Expand All @@ -124,17 +131,34 @@ def verify_cert(self):
except IOError:
log.error('Unable to open either certificate or key file')
raise
log.debug('Certificate looks good.')

def _create_skt(self):
log.debug('Creating the auth socket')
if ':' in self.auth_address:
self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.socket.bind((self.auth_address, self.auth_port))
except socket.error as msg:
error_string = 'Unable to bind (auth) to port {} on {}: {}'.format(self.auth_port, self.auth_address, msg)
log.error(error_string, exc_info=True)
raise BindException(error_string)

def start(self):
'''
Listen to auth requests and send the AES key.
Each client connection starts a new thread.
'''
# Start suicide polling thread
log.debug('Starting the auth process')
self.verify_cert()
thread = threading.Thread(target=self._suicide_when_without_parent, args=(os.getppid(),))
thread.start()
signal.signal(signal.SIGTERM, self._exit_gracefully)
self.__up = True
self._create_skt()
self.socket.listen(AUTH_MAX_CONN)
while self.__up:
try:
Expand Down
20 changes: 4 additions & 16 deletions napalm_logs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def _respawn_when_dead(self, start_fun, *args, **kwargs):
log.warning('%s (PID %d) restarted with PID %d', proc._name, pid, proc.pid)
pid = proc.pid

def _start_auth_proc(self, auth_skt):
def _start_auth_proc(self):
'''
Start the authenticator process.
'''
Expand All @@ -426,7 +426,8 @@ def _start_auth_proc(self, auth_skt):
self.keyfile,
self.__priv_key,
sgn_verify_hex,
auth_skt)
self.auth_address,
self.auth_port)
auth.verify_cert()
proc = Process(target=auth.start)
proc.start()
Expand Down Expand Up @@ -504,19 +505,6 @@ def start_engine(self):
'''
Start the child processes (one per device OS)
'''
# main listener socket
# auth proc section
log.debug('Creating the auth socket')
if ':' in self.auth_address:
auth_skt = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
auth_skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
auth_skt.bind((self.auth_address, self.auth_port))
except socket.error as msg:
error_string = 'Unable to bind (auth) to port {} on {}: {}'.format(self.auth_port, self.auth_address, msg)
log.error(error_string, exc_info=True)
raise BindException(error_string)
if self.disable_security is True:
log.warning('***Not starting the authenticator process due to disable_security being set to True***')
else:
Expand All @@ -525,7 +513,7 @@ def start_engine(self):
log.debug('Generating the signing key')
self.__signing_key = nacl.signing.SigningKey.generate()
# start the keepalive thread for the auth sub-process
self._processes.append(self._start_auth_proc(auth_skt))
self._processes.append(self._start_auth_proc())
# publisher process start
pub_pipe, dev_pub_pipe = Pipe(duplex=False)
self._processes.append(self._start_pub_proc(pub_pipe))
Expand Down
24 changes: 13 additions & 11 deletions napalm_logs/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
Config defaults.
'''
from __future__ import absolute_import
from __future__ import unicode_literals

import os
import logging
import napalm_logs.ext.six as six

# config
ROOT_DIR = '/'
Expand All @@ -18,6 +18,8 @@
PUBLISH_PORT = 49017
AUTH_ADDRESS = '0.0.0.0'
AUTH_PORT = 49018
AUTH_MAX_TRY = 1
AUTH_TIMEOUT = 1
LOG_LEVEL = 'warning'
LOG_FORMAT = '%(asctime)s,%(msecs)03.0f [%(name)-17s][%(levelname)-8s] %(message)s'
LOG_FILE = os.path.join(ROOT_DIR, 'var', 'log', 'napalm', 'logs')
Expand Down Expand Up @@ -62,21 +64,21 @@
'prefixes': [
{
'values': {
'tag': basestring
'tag': six.string_type
},
'line': basestring
'line': six.string_type
}
],
'messages': [
{
# 'error' should be unique and vendor agnostic. Currently we are using the JUNOS syslog message name as the canonical name.
# This may change if we are able to find a more well defined naming system.
'error': basestring,
'tag': basestring,
'error': six.string_type,
'tag': six.string_type,
'values': dict,
'replace': dict,
'line': basestring,
'model': basestring,
'line': six.string_type,
'model': six.string_type,
'mapping': {
'variables': dict,
'static': dict
Expand Down Expand Up @@ -109,13 +111,13 @@
PUB_IPC_URL = 'ipc:///tmp/napalm-logs-pub'

# auth
AUTH_KEEP_ALIVE = 'KEEPALIVE'
AUTH_KEEP_ALIVE_ACK = 'KEEPALIVEACK'
AUTH_KEEP_ALIVE = b'KEEPALIVE'
AUTH_KEEP_ALIVE_ACK = b'KEEPALIVEACK'
AUTH_KEEP_ALIVE_INTERVAL = 10
AUTH_MAX_CONN = 5
AUTH_TIMEOUT = 5
MAGIC_ACK = 'ACK'
MAGIC_REQ = 'INIT'
MAGIC_ACK = b'ACK'
MAGIC_REQ = b'INIT'
AUTH_CIPHER = 'ECDHE-RSA-AES256-GCM-SHA384'

OPEN_CONFIG_NO_MODEL = 'NO_MODEL'
Expand Down
7 changes: 7 additions & 0 deletions napalm_logs/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,10 @@ class SSLMismatchException(NapalmLogsException):
Raised when the SSL certificate and key do not match
'''
pass


class ClientConnectException(NapalmLogsException):
'''
Raised when the client is unable to connect.
'''
pass
Empty file added napalm_logs/ext/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions napalm_logs/ext/six.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
'''
Python 2-3 compatibility.
'''
from __future__ import absolute_import
from __future__ import unicode_literals

import sys

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
string_type = str
text_type = str
binary_type = bytes
else:
string_type = basestring
text_type = unicode
binary_type = str
34 changes: 28 additions & 6 deletions napalm_logs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

# Import napalm-logs pkgs
import napalm_logs.config as defaults
from napalm_logs.exceptions import ClientConnectException
from napalm_logs.exceptions import CryptoException
from napalm_logs.exceptions import BadSignatureException

Expand All @@ -40,15 +41,21 @@ class ClientAuth:
def __init__(self,
certificate,
address=defaults.AUTH_ADDRESS,
port=defaults.AUTH_PORT):
port=defaults.AUTH_PORT,
timeout=defaults.AUTH_TIMEOUT,
max_try=defaults.AUTH_MAX_TRY):
self.certificate = certificate
self.address = address
self.port = port
self.timeout = timeout
self.max_try = max_try
self.try_id = 0
self.priv_key = None
self.verify_key = None
self.ssl_skt = None
self.authenticate()
self._start_keep_alive()
self.__up = True

def _start_keep_alive(self):
'''
Expand All @@ -63,7 +70,8 @@ def keep_alive(self):
Send a keep alive request periodically to make sure that the server
is still alive. If not then try to reconnect.
'''
while True:
self.ssl_skt.settimeout(defaults.AUTH_KEEP_ALIVE_INTERVAL)
while self.__up:
self.ssl_skt.send(defaults.AUTH_KEEP_ALIVE)
msg = self.ssl_skt.recv(len(defaults.AUTH_KEEP_ALIVE_ACK))
if msg != defaults.AUTH_KEEP_ALIVE_ACK:
Expand All @@ -75,7 +83,7 @@ def reconnect(self):
'''
Try to reconnect and re-authenticate with the server.
'''
while True:
while self.__up:
try:
self.authenticate()
except socket.error:
Expand All @@ -98,9 +106,17 @@ def authenticate(self):
skt_ver = socket.AF_INET
skt = socket.socket(skt_ver, socket.SOCK_STREAM)
self.ssl_skt = ssl.wrap_socket(skt,
ca_certs=self.certificate,
cert_reqs=ssl.CERT_REQUIRED)
self.ssl_skt.connect((self.address, self.port))
ca_certs=self.certificate,
cert_reqs=ssl.CERT_REQUIRED)
try:
self.ssl_skt.connect((self.address, self.port))
except socket.error as err:
self.try_id += 1
if self.try_id < self.max_try:
time.sleep(self.timeout)
self.authenticate()
raise ClientConnectException(err)

# Explicit INIT
self.ssl_skt.write(defaults.MAGIC_REQ)
# Receive the private key
Expand Down Expand Up @@ -133,6 +149,12 @@ def decrypt(self, binary):
raise CryptoException('Unable to decrypt')
return umsgpack.unpackb(packed)

def stop(self):
'''
Stop the client.
'''
self.__up = False


def unserialize(binary):
'''
Expand Down
7 changes: 7 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
-r requirements.txt
coveralls
pytest
pytest-cov
pytest-json
pytest-pythonpath
pytest-travis-fold

6 changes: 6 additions & 0 deletions setup.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[pytest]
log_format = %(asctime)s,%(msecs)03.0f [%(name)-17s][%(levelname)-8s] %(message)s
log_date_format = %Y-%m-%d %H:%M:%S
addopts = -v --cov=napalm_logs --cov-report
json_report = report.json
jsonapi = true
21 changes: 21 additions & 0 deletions tests/auth/forged.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDsDCCApgCCQCD5IOMa6tvajANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UEBhMC
EzARBgNVBAoMCkNsb3VkZmxhcmUxDDAKBgNVBAsMA05FVDEUMBIGA1UEAwwLTkFQ
QUxNIGxvZ3MxJDAiBgkqhkiG9w0BCQEWFW1pcmNlYUBjbG91ZGZsYXJlLmNvbTAe
Fw0xNzA0MjExMTU5NDNaFw0xODA0MjExMTU5NDNaMIGZMQswCQYDVQQGEwJVSzEX
MBUGA1UECAwOR3JlYXRlciBMb25kb24xEjAQBgNVBAcMCUdyZWVud2ljaDETMBEG
A1UECgwKQ2xvdWRmbGFyZTEMMAoGA1UECwwDTkVUMRQwEgYDVQQDDAtOQVBBTE0g
bG9nczEkMCIGCSqGSIb3DQEJARYVbWlyY2VhQGNsb3VkZmxhcmUuY29tMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3DRM7FYARhsU2/rPR7eGORR3qt1x
Z00ZjYesbPGsXMSCEvFvXe84QR/a/xUvar0fNuCeLBx9xSojYXzuTTbv5+R5m5Mq
FiXwAKOnWROHOx3FMuVF1hBFpM88CmpTT1exxy1y91NlrQc4cZp9X5W9I4Ir6yjq
gA8ZB57vlxXd5qToanKuh2KGfk2P5NygQcd56VnOylfc4MTsWt5GMgU4unrA7wDq
NcDqmd9o7TjFN55cC13KyrjQElt+g5EUD9zCvqvIRKgHkwHhHb6uXf6selOdLTfR
JzmZRZJBLBnrUaGaXwUCGyQNtPq+winaEYv7xdz5OJP3ktsctO0ZWBn3CQIDAQAB
MA0GCSqGSIb3DQEBCwUAA4IBAQBWSBIOHzCVVHRi6Pn/QlB61vK5vmCgOHVhWc0I
FjK8/EplvGUQ8gOUPTjPBoICvDVTTD3YJaVy4gNpNRuX0K6/RtufwJyhdbCAE9Rt
ROy7kolwXNc9wkIAqF3TvwCYOMYWGewq98R4cFClESG4Uy+u4TgqsGDTjNzussWD
GGfhib6yh79A26xM70AQAKYDl3aaNBunLGXwBUPSMvQXN5rNL+4hAuO43pmO7Btg
EnQq6jTrjaQnzIc1fabXSWTdA0cSDQUglCuDyYiPqeZnJlANcWOfK/grEt6vt3Tf
O1GRSoEzOUdCJ8rbFFLubJVmIIvtjUANlGDQVfZdpyFLGxAy
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions tests/auth/forged.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3DRM7FYARhsU2/rPR7eGORR3qt1xZ00ZjYesbPGsXMSCEvFv
Xe84QR/a/xUvar0fNuCeLBx9xSojYXzuTTbv5+R5m5MqFiXwAKOnWROHOx3FMuVF
1hBFpM88CmpTT1exxy1y91NlrQc4cZp9X5W9I4Ir6yjqgA8ZB57vlxXd5qToanKu
h2KGfk2P5NygQcd56VnOylfc4MTsWt5GMgU4unrA7wDqNcDqmd9o7TjFN55cC13K
yrjQElt+g5EUD9zCvqvIRKgHkwHhHb6uXf6selOdLTfRJzmZRZJBLBnrUaGaXwUC
GyQNtPq+winaEYv7xdz5OJP3ktsctO0ZWBn3CQIDAQABAoIBAGaguXksW9RQqtgb
fJ+gGR5hO6SWsFPEyzP7F5Fu0TYrH7RUceMFquIvRpG7/e1xVyrsZMuKO9O4X+T/
pNC32ffptAOeAu9vnvutSkFpqI0UwuOFl5a83riBHSp9g3ZnGKQLupkqdSi/RL8t
kWELGKuD75QZ7bDhWHkEygVQW6syMwzEWzSqQWw6oeFEdLNWOSXP2YugXY4mgWLB
xak14dgJIx59oavxv0AaXcNdbJsNYwjR/INiRZP58pnue2B/UtDV9QxOYf1c+2UU
Blw2fX9DAIqGN8eYqM16aX1izEg/d/Uzf6o7Ia2VDYyxRJxLSoTc5xGR+Sb4J7yV
DooHaAECgYEA78k3nBj/3Rriqaer4NmSmzlVvimIYPEcyLxM2kGBpDnsjjfevOxP
BNK7eAn++M560y0CsCKe3ne/3Te4+TYSp5WPIbbm56/hDjK4qS8URfu6SGdDebwE
ziFpVh3f8KI9eva536qW+EzzwrFvuCPIrQaUyZ9uL5yaHL6DG8YMz7ECgYEA6xgd
57YVgJEt5iOoc05eEYuZU1CI3qW7lneEnIa+wqaMXH27UdD+NUjewy2O9Aii0Cib
KPXj9bVupqC4rjyqstQEmNs8gyh3pfoMOibDBvdJRi1KSAPxvUb/0x7AiUbT0955
oIOjHE591ueRWs7rnXaruTMFPReACdirm2pxCtkCgYEA2DKi0zmvKNpe1/z3+gny
J8awSn8EGfQLseolmPxLOZmIckppqAmgzYZ7xSWuIQN6twD0VX9BovBxq7HcM8bP
Vw6AOTPCSc/IqZaN7O0n6FmpgjKpIg+mfXyejyjsividWksvGD2lVUd82WRGiM6q
12cBrDD1ljQquBQF/nq5fmECgYEA2HDWr4qrFzgSSuyPXMFMx9h/a/4cVOe+4hPd
SoKfw/O0DD9Ro0zHjeUt+gHKuA/vXbfUygxAOOo2GkCkZv3BpmyeYuCNQK+UIIIq
maswKQyoMGzoE49WE433X7bTSk+kHE6aKdan6Bq3rpB5+WHB+LHV/ebkLs+g+O+7
ZpXDx1kCgYB9qLGbvgNrl5vS54g9n/Zd6qrRTPuW84UWK84W3cyghxSJTuBMQy6R
cvQGhBgTdPG6J/5I0afewu9xLVdQOiLDzIhbjB3SXk+x0wk3f8649HW3vIo4nNsI
0juQnYvK3G4x7g+N42V/af6pAcojvAGbVX1rKCqzGdrgUHwVcUU+wA==
-----END RSA PRIVATE KEY-----

0 comments on commit a3faa02

Please sign in to comment.