Skip to content

Commit

Permalink
Fixed missing import copy.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Delle Cave committed Apr 13, 2020
1 parent 4efe45c commit 67ef4b2
Show file tree
Hide file tree
Showing 3 changed files with 478 additions and 0 deletions.
108 changes: 108 additions & 0 deletions covenant/modules/probes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018-2019 fjord-technologies
# SPDX-License-Identifier: GPL-3.0-or-later
"""covenant.modules.probes"""

import gc
import logging
import time
import uuid

from dwho.classes.modules import DWhoModuleBase, MODULES
from httpdis.httpdis import HttpReqError, HttpResponse
from sonicprobe.libs import xys
from sonicprobe.libs.moresynchro import RWLock

from covenant.classes.plugins import CovenantEPTObject, EPTS_SYNC

LOG = logging.getLogger('covenant.modules.probes')


# pylint: disable=attribute-defined-outside-init
class ProbesModule(DWhoModuleBase):
MODULE_NAME = 'probes'

LOCK = RWLock()

def safe_init(self, options):
self.results = {}
self.lock_timeout = self.config['general']['lock_timeout']

def _set_result(self, obj):
self.results[obj.get_uid()] = obj

def _get_result(self, uid):
r = {'error': None,
'result': None}

while True:
if uid not in self.results:
time.sleep(0.1)
continue

res = self.results.pop(uid)
if res.has_error():
r['error'] = res.get_errors()
LOG.error("failed on call: %r. (errors: %r)", res.get_uid(), r['error'])
else:
r['result'] = res.get_result()
LOG.info("successful on call: %r", res.get_uid())
LOG.debug("result on call: %r", r['result'])

return r

def _push_epts_sync(self, endpoint, method, params, args = None):
if endpoint not in EPTS_SYNC:
raise HttpReqError(404, "unable to find endpoint: %r" % endpoint)
elif EPTS_SYNC[endpoint].type != 'probe':
raise HttpReqError(400, "invalid endpoint type, correct type: %r" % EPTS_SYNC[endpoint].type)

ept_sync = EPTS_SYNC[endpoint]
uid = "%s:%s" % (ept_sync.name, uuid.uuid4())
ept_sync.qput(CovenantEPTObject(ept_sync.name,
uid,
endpoint,
method,
params,
args,
self._set_result))
return uid


PROBES_QSCHEMA = xys.load("""
endpoint: !!str
target*: !!str
""")

def probes(self, request):
params = request.query_params()

if not isinstance(params, dict):
raise HttpReqError(400, "invalid arguments type")

if not xys.validate(params, self.PROBES_QSCHEMA):
raise HttpReqError(415, "invalid arguments for command")

if not self.LOCK.acquire_read(self.lock_timeout):
raise HttpReqError(503, "unable to take LOCK for reading after %s seconds" % self.lock_timeout)

try:
uid = self._push_epts_sync(params['endpoint'], 'probes', params)
res = self._get_result(uid)
if res['error']:
raise HttpReqError(500, "failed to get results. (errors: %r)" % res['error'])

return HttpResponse(data = res['result'])
except HttpReqError:
raise
except Exception as e:
LOG.exception(e)
finally:
gc.collect()
self.LOCK.release()


if __name__ != "__main__":
def _start():
MODULES.register(ProbesModule())
_start()
243 changes: 243 additions & 0 deletions covenant/plugins/pssl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 fjord-technologies
# SPDX-License-Identifier: GPL-3.0-or-later
"""covenant.plugins.pssl"""

import logging
import socket
import ssl

from datetime import datetime

from cryptography import x509
from cryptography.hazmat.backends import default_backend

import six

from sonicprobe.libs import network, urisup

from covenant.classes.exceptions import CovenantConfigurationError
from covenant.classes.plugins import CovenantPlugBase, CovenantTargetFailed, PLUGINS

LOG = logging.getLogger('covenant.plugins.ssl')

_ALLOWED_OPTIONS = ('ALL',
'NO_SSLv2',
'NO_SSLv3',
'NO_TLSv1',
'NO_TLSv1_1',
'NO_TLSv1_2',
'NO_TLSv1_3',
'NO_COMPRESSION',
'CIPHER_SERVER_PREFERENCE',
'SINGLE_DH_USE',
'SINGLE_ECDH_USE',
'ENABLE_MIDDLEBOX_COMPAT')


class CovenantSslPlugin(CovenantPlugBase):
PLUGIN_NAME = 'ssl'

@staticmethod
def _dnsname_to_idn(name):
for x in ('*.', '.'):
if name.startswith(x):
return "%s%s" % (x, network.encode_idn(name[len(x):], True))

return network.encode_idn(name, True)

@staticmethod
def _valid_hostname(valid_hosts, host):
try:
ssl.match_hostname(valid_hosts, host)
except ssl.CertificateError:
return False

return True

@staticmethod
def _load_cert(cert_der, rs = None):
if not isinstance(rs, dict):
rs = {}

cert = x509.load_der_x509_certificate(six.ensure_str(cert_der), default_backend())

rs['connect_success'] = True
rs['cert_not_before'] = int(cert.not_valid_before.strftime('%s'))
rs['cert_not_after'] = int(cert.not_valid_after.strftime('%s'))
rs['cert_has_expired'] = cert.not_valid_after < datetime.utcnow()
rs['cert_serial_no'] = cert.serial_number
rs['cert_issuer_cn'] = cert.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
rs['cert_cn'] = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
rs['cert_emails'] = []
rs['cert_dns_names'] = []
rs['cert_ip_addresses'] = []

for x in cert.subject.get_attributes_for_oid(x509.OID_EMAIL_ADDRESS):
rs['cert_emails'].append(six.ensure_str(x.value))

rs['cert_ou'] = []
for x in cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATIONAL_UNIT_NAME):
rs['cert_ou'].append(six.ensure_str(x.value))

return (cert, rs)

@staticmethod
def _load_context_options(context, options):
if not isinstance(options, list):
return

for x in options:
if x not in _ALLOWED_OPTIONS:
LOG.warning("invalid ssl option: OP_%s", x)
continue

if not hasattr(ssl, "OP_%s" % x):
LOG.warning("unknown ssl option: OP_%s", x)
continue

context.options |= getattr(ssl, "OP_%s" % x)

@staticmethod
def _connect(context, host, port, server_hostname, timeout):
conn = context.wrap_socket(
socket.socket(socket.AF_INET),
server_hostname = server_hostname)

conn.settimeout(timeout)
conn.connect((host, int(port)))

return conn

def _subject_alt_name(self, cert, rs = None, valid_hosts = None):
if not isinstance(rs, dict):
rs = {}

if not isinstance(valid_hosts, dict):
valid_hosts = {}

try:
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
except Exception:
return (rs, valid_hosts)

for x in ext.value:
if isinstance(x, x509.RFC822Name):
rs['cert_emails'].append(six.ensure_str(x.value))
elif isinstance(x, x509.DNSName):
rs['cert_dns_names'].append(six.ensure_str(x.value))
valid_hosts['subjectAltName'].append(('DNS', self._dnsname_to_idn(x.value)))
elif isinstance(x, x509.IPAddress):
rs['cert_ip_addresses'].append(six.ensure_str(x.value.compressed))
valid_hosts['subjectAltName'].append(('IP Address', six.ensure_str(x.value.compressed)))

return (rs, valid_hosts)

def _do_call(self, obj, targets = None, registry = None): # pylint: disable=unused-argument
if not targets:
targets = self.targets

for target in targets:
(data, conn) = (None, None)

cfg = target.config
common_name = cfg.get('common_name')
cfg['timeout'] = cfg.get('timeout', 10)
cfg['verify_peer'] = cfg.get('verify_peer', True)
params = obj.get_params()

if not params.get('target'):
uri = cfg.get('uri')
else:
uri = params['target']

if not uri:
raise CovenantConfigurationError("missing target or uri in configuration")

uri_split = urisup.uri_help_split(uri)
scheme = None

if not isinstance(uri_split[1], tuple):
host = uri_split[0]
port = uri_split[2]
elif uri_split[1]:
scheme = uri_split[0]
host, port = uri_split[1][2:4]
else:
raise CovenantConfigurationError("missing host and port in uri: %r" % uri)

if not host:
raise CovenantConfigurationError("missing or invalid host in uri: %r" % uri)

if scheme and not port:
try:
port = socket.getservbyname(scheme)
except socket.error:
pass

if not port:
raise CovenantConfigurationError("missing or invalid port in uri: %r" % uri)

data = {'connect_success': False,
'cert_secure': False,
"%s_success" % self.type: False}

try:
server_hostname = common_name or host

context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

self._load_context_options(context, cfg.get('options'))

conn = self._connect(context, host, port, server_hostname, cfg['timeout'])

data['cipher_info'] = conn.cipher()[0]
data['version_info'] = conn.version()

valid_hosts = {'subject': [],
'subjectAltName': []}

cert_der = conn.getpeercert(True)
if cert_der:
self._subject_alt_name(self._load_cert(cert_der, data)[0], data, valid_hosts)
data['hostname_valid'] = self._valid_hostname(valid_hosts, server_hostname)

if cfg['verify_peer']:
if conn:
conn.close()

context.verify_mode = ssl.CERT_REQUIRED
context.load_default_certs(ssl.Purpose.SERVER_AUTH)
conn = self._connect(context, host, port, server_hostname, cfg['timeout'])
except ssl.SSLError as e:
LOG.warning("ssl error on target: %r. exception: %r",
target.name,
e)
except Exception as e:
data = CovenantTargetFailed(e)
LOG.exception("error on target: %r. exception: %r",
target.name,
e)
else:
if data.get('connect_success'):
if not cfg['verify_peer']:
data["%s_success" % self.type] = True
elif not data.get('cert_has_expired') \
and data.get('hostname_valid'):
data['cert_secure'] = True
data["%s_success" % self.type] = True
finally:
if conn:
conn.close()

target(data)

return self.generate_latest(registry)


if __name__ != "__main__":
def _start():
PLUGINS.register(CovenantSslPlugin)
_start()

0 comments on commit 67ef4b2

Please sign in to comment.