Skip to content

Commit

Permalink
[http_check] Added supprot for weak cyphers.
Browse files Browse the repository at this point in the history
Session object has nearly identical request method.

Default value for weakcipher is false.

Adding debug logging for weak ciphers.

Importing datetime differently to urllib3 code.

Added Transport adapter for weak cyphers, requests lib sessions required too.

Session object has nearly identical request method.

Default value for weakcipher is false.

Adding debug logging for weak ciphers.

Importing datetime differently to urllib3 code.

Changing cipherlist for weak ciphers, now it works.

Fixing test_http_check.py to contemplate new weakciphers config param.

fixing flake8 issue.

More flake8 issue.

Assuming time is correct and python>=2.6.3

We still need that getattr check in 2.7
  • Loading branch information
truthbk committed Nov 16, 2015
1 parent cf70ff0 commit b76490d
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 6 deletions.
123 changes: 118 additions & 5 deletions checks.d/http_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,121 @@
import socket
import ssl
import time
import warnings
from urlparse import urlparse

# 3rd party
import requests
import tornado

from requests.adapters import HTTPAdapter
from requests.packages import urllib3
from requests.packages.urllib3.util import ssl_

from requests.packages.urllib3.exceptions import (
SecurityWarning,
)
from requests.packages.urllib3.packages.ssl_match_hostname import \
match_hostname

# project
from checks.network_checks import EventType, NetworkCheck, Status
from config import _is_affirmative
from util import headers as agent_headers


class WeakCiphersHTTPSConnection(urllib3.connection.VerifiedHTTPSConnection):

SUPPORTED_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:'
'ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:'
'RSA+3DES:ECDH+RC4:DH+RC4:RSA+RC4:!aNULL:!eNULL:!EXP:-MD5:RSA+RC4+MD5'
)

def __init__(self, host, port, ciphers=None, **kwargs):
self.ciphers = ciphers if ciphers is not None else self.SUPPORTED_CIPHERS
super(WeakCiphersHTTPSConnection, self).__init__(host, port, **kwargs)

def connect(self):
# Add certificate verification
conn = self._new_conn()

resolved_cert_reqs = ssl_.resolve_cert_reqs(self.cert_reqs)
resolved_ssl_version = ssl_.resolve_ssl_version(self.ssl_version)

hostname = self.host
if getattr(self, '_tunnel_host', None):
# _tunnel_host was added in Python 2.6.3
# (See:
# http://hg.python.org/cpython/rev/0f57b30a152f)
#
# However this check is still necessary in 2.7.x

self.sock = conn
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()
# Mark this connection as not reusable
self.auto_open = 0

# Override the host with the one we're requesting data from.
hostname = self._tunnel_host

# Wrap socket using verification with the root certs in trusted_root_certs
self.sock = ssl_.ssl_wrap_socket(conn, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
server_hostname=hostname,
ssl_version=resolved_ssl_version,
ciphers=self.ciphers)

if self.assert_fingerprint:
ssl_.assert_fingerprint(self.sock.getpeercert(binary_form=True), self.assert_fingerprint)
elif resolved_cert_reqs != ssl.CERT_NONE \
and self.assert_hostname is not False:
cert = self.sock.getpeercert()
if not cert.get('subjectAltName', ()):
warnings.warn((
'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. '
'This feature is being removed by major browsers and deprecated by RFC 2818. '
'(See https://github.com/shazow/urllib3/issues/497 for details.)'),
SecurityWarning
)
match_hostname(cert, self.assert_hostname or hostname)

self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
or self.assert_fingerprint is not None)


class WeakCiphersHTTPSConnectionPool(urllib3.connectionpool.HTTPSConnectionPool):

ConnectionCls = WeakCiphersHTTPSConnection


class WeakCiphersPoolManager(urllib3.poolmanager.PoolManager):

def _new_pool(self, scheme, host, port):
if scheme == 'https':
return WeakCiphersHTTPSConnectionPool(host, port, **(self.connection_pool_kw))
return super(WeakCiphersPoolManager, self)._new_pool(scheme, host, port)


class WeakCiphersAdapter(HTTPAdapter):
""""Transport adapter" that allows us to use TLS_RSA_WITH_RC4_128_MD5."""

def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
# Rewrite of the
# requests.adapters.HTTPAdapter.init_poolmanager method
# to use WeakCiphersPoolManager instead of
# urllib3's PoolManager
self._pool_connections = connections
self._pool_maxsize = maxsize
self._pool_block = block

self.poolmanager = WeakCiphersPoolManager(num_pools=connections,
maxsize=maxsize, block=block, strict=True, **pool_kwargs)


def get_ca_certs_path():
"""
Get a path to the trusted certificates of the system
Expand Down Expand Up @@ -61,28 +164,38 @@ def _load_conf(self, instance):
ssl = _is_affirmative(instance.get('disable_ssl_validation', True))
ssl_expire = _is_affirmative(instance.get('check_certificate_expiration', True))
instance_ca_certs = instance.get('ca_certs', self.ca_certs)
weakcipher = _is_affirmative(instance.get('weakciphers', False))

return url, username, password, http_response_status_code, timeout, include_content,\
headers, response_time, content_match, tags, ssl, ssl_expire, instance_ca_certs
headers, response_time, content_match, tags, ssl, ssl_expire, instance_ca_certs,\
weakcipher

def _check(self, instance):
addr, username, password, http_response_status_code, timeout, include_content, headers,\
response_time, content_match, tags, disable_ssl_validation,\
ssl_expire, instance_ca_certs = self._load_conf(instance)
ssl_expire, instance_ca_certs, weakcipher = self._load_conf(instance)
start = time.time()

service_checks = []
try:
parsed_uri = urlparse(addr)
self.log.debug("Connecting to %s" % addr)
if disable_ssl_validation and urlparse(addr)[0] == "https":
if disable_ssl_validation and parsed_uri.scheme == "https":
self.warning("Skipping SSL certificate validation for %s based on configuration"
% addr)

auth = None
if username is not None and password is not None:
auth = (username, password)

r = requests.get(addr, auth=auth, timeout=timeout, headers=headers,
sess = requests.Session()
if weakcipher:
base_addr = '{uri.scheme}://{uri.netloc}/'.format(uri=parsed_uri)
sess.mount(base_addr, WeakCiphersAdapter())
self.log.debug("Weak Ciphers will be used for {0}. Suppoted Cipherlist: {1}".format(
base_addr, WeakCiphersHTTPSConnection.SUPPORTED_CIPHERS))

r = sess.request('GET', addr, auth=auth, timeout=timeout, headers=headers,
verify=False if disable_ssl_validation else instance_ca_certs)

except (socket.timeout, requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
Expand Down Expand Up @@ -156,7 +269,7 @@ def _check(self, instance):
self.SC_STATUS, Status.UP, "UP"
))

if ssl_expire and urlparse(addr)[0] == "https":
if ssl_expire and parsed_uri.scheme == "https":
status, msg = self.check_cert_expiration(instance, timeout, instance_ca_certs)
service_checks.append((
self.SC_SSL_CERT, status, msg
Expand Down
2 changes: 1 addition & 1 deletion tests/checks/integration/test_http_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_http_headers(self):
url, username, password, http_response_status_code, timeout,\
include_content, headers, response_time, content_match,\
tags, ssl, ssl_expiration,\
instance_ca_certs = self.check._load_conf(CONFIG_HTTP_HEADERS['instances'][0])
instance_ca_certs, weakciphers = self.check._load_conf(CONFIG_HTTP_HEADERS['instances'][0])

self.assertEqual(headers["X-Auth-Token"], "SOME-AUTH-TOKEN", headers)
expected_headers = agent_headers(AGENT_CONFIG).get('User-Agent')
Expand Down

0 comments on commit b76490d

Please sign in to comment.