Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ https://github.com/elastic/apm-agent-python/compare/v1.0.0.dev3\...master[Check
* added `max-event-queue-length` setting. ({pull}67[#67])
* 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])
* changed Celery integration to store the task state (e.g. `SUCCESS` or `FAILURE`) in `transaction.result` ({pull}100[#100])
* added setting to disable SSL certificate verification ({pull}108[#108])
* BREAKING: renamed `server` configuration variable to `server_url` to better align with other language agents ({pull}105[#105])
* BREAKING: removed the old and unused urllib2-based HTTP transport, and renamed the urllib3 transport ({pull}107[#107])

Expand Down
14 changes: 14 additions & 0 deletions docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,17 @@ If set to `True`, the agent won't send any events to the APM server, independent

If set to `True`, the agent won't instrument any code.
This disables most of the tracing functionality, but can be useful to debug possible instrumentation issues.


[float]
[[config-verify-server-cert]]
==== `verify_server_cert`
|============
| Environment | Django/Flask | Default
| `ELASTIC_APM_VERIFY_SERVER_CERT` | `VERIFY_SERVER_CERT` | `True`
|============

By default, the agent verifies the SSL certificate if you use an HTTPS connection to the APM server.
Verification can be disabled by changing this setting to `False`.

NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python 3.4.3+.
4 changes: 3 additions & 1 deletion elasticapm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,9 @@ def _get_transport(self, parsed_url):
'process. PID: %s', os.getpid())
return self._transport_class.sync_transport(parsed_url)
if parsed_url not in self._transports:
self._transports[parsed_url] = self._transport_class(parsed_url)
self._transports[parsed_url] = self._transport_class(
parsed_url, verify_server_cert=self.config.verify_server_cert
)
return self._transports[parsed_url]

def _filter_exception_type(self, data):
Expand Down
1 change: 1 addition & 0 deletions elasticapm/conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class Config(_ConfigBase):
secret_token = _ConfigValue('SECRET_TOKEN')
debug = _BoolConfigValue('DEBUG', default=False)
server_url = _ConfigValue('SERVER_URL', default='http://localhost:8200', required=True)
verify_server_cert = _BoolConfigValue('VERIFY_SERVER_CERT', default=True)
include_paths = _ListConfigValue('INCLUDE_PATHS')
exclude_paths = _ListConfigValue('EXCLUDE_PATHS', default=['elasticapm'])
filter_exception_types = _ListConfigValue('FILTER_EXCEPTION_TYPES')
Expand Down
9 changes: 6 additions & 3 deletions elasticapm/transport/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ class AsyncioHTTPTransport(HTTPTransportBase):
HTTP Transport ready for asyncio
"""

def __init__(self, parsed_url):
super(AsyncioHTTPTransport, self).__init__(parsed_url)
def __init__(self, parsed_url, **kwargs):
super(AsyncioHTTPTransport, self).__init__(parsed_url, **kwargs)
loop = asyncio.get_event_loop()
self.client = aiohttp.ClientSession(loop=loop)
session_kwargs = {'loop': loop}
if not self._verify_server_cert:
session_kwargs['connector'] = aiohttp.TCPConnector(verify_ssl=False)
self.client = aiohttp.ClientSession(**session_kwargs)

async def send(self, data, headers, timeout=None):
"""Use synchronous interface, because this is a coroutine."""
Expand Down
14 changes: 9 additions & 5 deletions elasticapm/transport/http.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import logging
import os
import ssl

import certifi
import urllib3
Expand All @@ -19,18 +20,21 @@ class Transport(HTTPTransportBase):

scheme = ['http', 'https']

def __init__(self, parsed_url):
kwargs = {
def __init__(self, parsed_url, **kwargs):
super(Transport, self).__init__(parsed_url, **kwargs)
pool_kwargs = {
'cert_reqs': 'CERT_REQUIRED',
'ca_certs': certifi.where(),
'block': True,
}
if not self._verify_server_cert:
pool_kwargs['cert_reqs'] = ssl.CERT_NONE
pool_kwargs['assert_hostname'] = False
proxy_url = os.environ.get('HTTPS_PROXY', os.environ.get('HTTP_PROXY'))
if proxy_url:
self.http = urllib3.ProxyManager(proxy_url, **kwargs)
self.http = urllib3.ProxyManager(proxy_url, **pool_kwargs)
else:
self.http = urllib3.PoolManager(**kwargs)
super(Transport, self).__init__(parsed_url)
self.http = urllib3.PoolManager(**pool_kwargs)

def send(self, data, headers, timeout=None):
if timeout is None:
Expand Down
7 changes: 4 additions & 3 deletions elasticapm/transport/http_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
class HTTPTransportBase(Transport):
scheme = ['http', 'https']

def __init__(self, parsed_url):
def __init__(self, parsed_url, verify_server_cert=True):
self.check_scheme(parsed_url)

self._parsed_url = parsed_url
self._url = parsed_url.geturl()
self._verify_server_cert = verify_server_cert

def send(self, data, headers, timeout=None):
"""
Expand All @@ -26,8 +27,8 @@ class AsyncHTTPTransportBase(AsyncTransport, HTTPTransportBase):
async_mode = True
sync_transport = HTTPTransportBase

def __init__(self, parsed_url):
super(AsyncHTTPTransportBase, self).__init__(parsed_url)
def __init__(self, parsed_url, **kwargs):
super(AsyncHTTPTransportBase, self).__init__(parsed_url, **kwargs)
if self._url.startswith('async+'):
self._url = self._url[6:]
self._worker = None
Expand Down
22 changes: 22 additions & 0 deletions tests/asyncio/test_asyncio_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,25 @@ async def test_send_timeout(mock_client):
with pytest.raises(TransportException) as excinfo:
await transport.send(b'data', {}, timeout=0.0001)
assert 'Connection to APM Server timed out' in str(excinfo.value)


@pytest.mark.asyncio
async def test_ssl_verify_fails(httpsserver):
from elasticapm.transport.asyncio import AsyncioHTTPTransport
from elasticapm.transport.base import TransportException

httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
transport = AsyncioHTTPTransport(urlparse(httpsserver.url))
with pytest.raises(TransportException) as exc_info:
await transport.send(b'x', {})
assert 'CERTIFICATE_VERIFY_FAILED' in str(exc_info)


@pytest.mark.asyncio
async def test_ssl_verify_disable(httpsserver):
from elasticapm.transport.asyncio import AsyncioHTTPTransport

httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
transport = AsyncioHTTPTransport(urlparse(httpsserver.url), verify_server_cert=False)
url = await transport.send(b'x', {})
assert url == 'http://example.com/foo'
6 changes: 6 additions & 0 deletions tests/client/client_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ def test_client_uses_sync_mode_when_master_process(is_master_process):
assert transport.async_mode is False


@pytest.mark.parametrize('elasticapm_client', [{'verify_server_cert': False}], indirect=True)
def test_client_disables_ssl_verification(elasticapm_client):
assert not elasticapm_client.config.verify_server_cert
assert not elasticapm_client._get_transport(compat.urlparse.urlparse('https://example.com'))._verify_server_cert


@pytest.mark.parametrize('elasticapm_client', [{'transactions_ignore_patterns': [
'^OPTIONS',
'views.api.v2'
Expand Down
16 changes: 16 additions & 0 deletions tests/transports/test_urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,19 @@ def test_header_encodings():
for k, v in kwargs['headers'].items():
assert isinstance(k, compat.binary_type)
assert isinstance(v, compat.binary_type)


def test_ssl_verify_fails(httpsserver):
httpsserver.serve_content(code=202, content='', headers={'Location': 'http://example.com/foo'})
transport = Transport(compat.urlparse.urlparse(httpsserver.url))
with pytest.raises(TransportException) as exc_info:
url = transport.send(compat.b('x'), {})
assert 'CERTIFICATE_VERIFY_FAILED' in str(exc_info)


@pytest.mark.filterwarnings('ignore:Unverified HTTPS')
def test_ssl_verify_disable(httpsserver):
httpsserver.serve_content(code=202, content='', headers={'Location': 'https://example.com/foo'})
transport = Transport(compat.urlparse.urlparse(httpsserver.url), verify_server_cert=False)
url = transport.send(compat.b('x'), {})
assert url == 'https://example.com/foo'