Skip to content

Commit 57f41c7

Browse files
committed
added setting to disable SSL certificate verification (#108)
closes #108
1 parent 4ac0b94 commit 57f41c7

File tree

10 files changed

+82
-12
lines changed

10 files changed

+82
-12
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ https://github.com/elastic/apm-agent-python/compare/v1.0.0.dev3\...master[Check
1515
* added `max-event-queue-length` setting. ({pull}67[#67])
1616
* changed name that the agent reports itself with to the APM server from `elasticapm-python` to `python`. This aligns the Python agent with other languages. ({pull}104[#104])
1717
* changed Celery integration to store the task state (e.g. `SUCCESS` or `FAILURE`) in `transaction.result` ({pull}100[#100])
18+
* added setting to disable SSL certificate verification ({pull}108[#108])
1819
* BREAKING: renamed `server` configuration variable to `server_url` to better align with other language agents ({pull}105[#105])
1920
* BREAKING: removed the old and unused urllib2-based HTTP transport, and renamed the urllib3 transport ({pull}107[#107])
2021

docs/configuration.asciidoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,17 @@ If set to `True`, the agent won't send any events to the APM server, independent
352352

353353
If set to `True`, the agent won't instrument any code.
354354
This disables most of the tracing functionality, but can be useful to debug possible instrumentation issues.
355+
356+
357+
[float]
358+
[[config-verify-server-cert]]
359+
==== `verify_server_cert`
360+
|============
361+
| Environment | Django/Flask | Default
362+
| `ELASTIC_APM_VERIFY_SERVER_CERT` | `VERIFY_SERVER_CERT` | `True`
363+
|============
364+
365+
By default, the agent verifies the SSL certificate if you use an HTTPS connection to the APM server.
366+
Verification can be disabled by changing this setting to `False`.
367+
368+
NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python 3.4.3+.

elasticapm/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ def _get_transport(self, parsed_url):
307307
'process. PID: %s', os.getpid())
308308
return self._transport_class.sync_transport(parsed_url)
309309
if parsed_url not in self._transports:
310-
self._transports[parsed_url] = self._transport_class(parsed_url)
310+
self._transports[parsed_url] = self._transport_class(
311+
parsed_url, verify_server_cert=self.config.verify_server_cert
312+
)
311313
return self._transports[parsed_url]
312314

313315
def _filter_exception_type(self, data):

elasticapm/conf/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class Config(_ConfigBase):
136136
secret_token = _ConfigValue('SECRET_TOKEN')
137137
debug = _BoolConfigValue('DEBUG', default=False)
138138
server_url = _ConfigValue('SERVER_URL', default='http://localhost:8200', required=True)
139+
verify_server_cert = _BoolConfigValue('VERIFY_SERVER_CERT', default=True)
139140
include_paths = _ListConfigValue('INCLUDE_PATHS')
140141
exclude_paths = _ListConfigValue('EXCLUDE_PATHS', default=['elasticapm'])
141142
filter_exception_types = _ListConfigValue('FILTER_EXCEPTION_TYPES')

elasticapm/transport/asyncio.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ class AsyncioHTTPTransport(HTTPTransportBase):
1111
HTTP Transport ready for asyncio
1212
"""
1313

14-
def __init__(self, parsed_url):
15-
super(AsyncioHTTPTransport, self).__init__(parsed_url)
14+
def __init__(self, parsed_url, **kwargs):
15+
super(AsyncioHTTPTransport, self).__init__(parsed_url, **kwargs)
1616
loop = asyncio.get_event_loop()
17-
self.client = aiohttp.ClientSession(loop=loop)
17+
session_kwargs = {'loop': loop}
18+
if not self._verify_server_cert:
19+
session_kwargs['connector'] = aiohttp.TCPConnector(verify_ssl=False)
20+
self.client = aiohttp.ClientSession(**session_kwargs)
1821

1922
async def send(self, data, headers, timeout=None):
2023
"""Use synchronous interface, because this is a coroutine."""

elasticapm/transport/http.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
import logging
33
import os
4+
import ssl
45

56
import certifi
67
import urllib3
@@ -18,18 +19,21 @@ class Transport(HTTPTransportBase):
1819

1920
scheme = ['http', 'https']
2021

21-
def __init__(self, parsed_url):
22-
kwargs = {
22+
def __init__(self, parsed_url, **kwargs):
23+
super(Transport, self).__init__(parsed_url, **kwargs)
24+
pool_kwargs = {
2325
'cert_reqs': 'CERT_REQUIRED',
2426
'ca_certs': certifi.where(),
2527
'block': True,
2628
}
29+
if not self._verify_server_cert:
30+
pool_kwargs['cert_reqs'] = ssl.CERT_NONE
31+
pool_kwargs['assert_hostname'] = False
2732
proxy_url = os.environ.get('HTTPS_PROXY', os.environ.get('HTTP_PROXY'))
2833
if proxy_url:
29-
self.http = urllib3.ProxyManager(proxy_url, **kwargs)
34+
self.http = urllib3.ProxyManager(proxy_url, **pool_kwargs)
3035
else:
31-
self.http = urllib3.PoolManager(**kwargs)
32-
super(Transport, self).__init__(parsed_url)
36+
self.http = urllib3.PoolManager(**pool_kwargs)
3337

3438
def send(self, data, headers, timeout=None):
3539
response = None

elasticapm/transport/http_base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
class HTTPTransportBase(Transport):
77
scheme = ['http', 'https']
88

9-
def __init__(self, parsed_url):
9+
def __init__(self, parsed_url, verify_server_cert=True):
1010
self.check_scheme(parsed_url)
1111

1212
self._parsed_url = parsed_url
1313
self._url = parsed_url.geturl()
14+
self._verify_server_cert = verify_server_cert
1415

1516
def send(self, data, headers, timeout=None):
1617
"""
@@ -26,8 +27,8 @@ class AsyncHTTPTransportBase(AsyncTransport, HTTPTransportBase):
2627
async_mode = True
2728
sync_transport = HTTPTransportBase
2829

29-
def __init__(self, parsed_url):
30-
super(AsyncHTTPTransportBase, self).__init__(parsed_url)
30+
def __init__(self, parsed_url, **kwargs):
31+
super(AsyncHTTPTransportBase, self).__init__(parsed_url, **kwargs)
3132
if self._url.startswith('async+'):
3233
self._url = self._url[6:]
3334
self._worker = None

tests/asyncio/test_asyncio_http.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,25 @@ async def test_send_timeout(mock_client):
8686
with pytest.raises(TransportException) as excinfo:
8787
await transport.send(b'data', {}, timeout=0.0001)
8888
assert 'Connection to APM Server timed out' in str(excinfo.value)
89+
90+
91+
@pytest.mark.asyncio
92+
async def test_ssl_verify_fails(httpsserver):
93+
from elasticapm.transport.asyncio import AsyncioHTTPTransport
94+
from elasticapm.transport.base import TransportException
95+
96+
httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
97+
transport = AsyncioHTTPTransport(urlparse(httpsserver.url))
98+
with pytest.raises(TransportException) as exc_info:
99+
await transport.send(b'x', {})
100+
assert 'CERTIFICATE_VERIFY_FAILED' in str(exc_info)
101+
102+
103+
@pytest.mark.asyncio
104+
async def test_ssl_verify_disable(httpsserver):
105+
from elasticapm.transport.asyncio import AsyncioHTTPTransport
106+
107+
httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
108+
transport = AsyncioHTTPTransport(urlparse(httpsserver.url), verify_server_cert=False)
109+
url = await transport.send(b'x', {})
110+
assert url == 'http://example.com/foo'

tests/client/client_tests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ def test_client_uses_sync_mode_when_master_process(is_master_process):
371371
assert transport.async_mode is False
372372

373373

374+
@pytest.mark.parametrize('elasticapm_client', [{'verify_server_cert': False}], indirect=True)
375+
def test_client_disables_ssl_verification(elasticapm_client):
376+
assert not elasticapm_client.config.verify_server_cert
377+
assert not elasticapm_client._get_transport(compat.urlparse.urlparse('https://example.com'))._verify_server_cert
378+
379+
374380
@pytest.mark.parametrize('elasticapm_client', [{'transactions_ignore_patterns': [
375381
'^OPTIONS',
376382
'views.api.v2'

tests/transports/test_urllib3.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,19 @@ def test_header_encodings():
9696
for k, v in kwargs['headers'].items():
9797
assert isinstance(k, compat.binary_type)
9898
assert isinstance(v, compat.binary_type)
99+
100+
101+
def test_ssl_verify_fails(httpsserver):
102+
httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
103+
transport = Transport(compat.urlparse.urlparse(httpsserver.url))
104+
with pytest.raises(TransportException) as exc_info:
105+
url = transport.send(compat.b('x'), {})
106+
assert 'CERTIFICATE_VERIFY_FAILED' in str(exc_info)
107+
108+
109+
@pytest.mark.filterwarnings('ignore:Unverified HTTPS')
110+
def test_ssl_verify_disable(httpsserver):
111+
httpsserver.serve_content(code=202, content='', headers={'Location': 'https://example.com/foo'})
112+
transport = Transport(compat.urlparse.urlparse(httpsserver.url), verify_server_cert=False)
113+
url = transport.send(compat.b('x'), {})
114+
assert url == 'https://example.com/foo'

0 commit comments

Comments
 (0)