Repository: elastic/apm-agent-python

Issue:
Add setting to ignore certifi if it's available
**Is your feature request related to a problem? Please describe.**
In some cases, it would be useful to tell the agent to ignore `certifi` and fall back to the certificate store provided by the operating system. 

**Describe the solution you'd like**
A setting that tells the agent to not use certifi.

- [x] implement new config option
- [x] add public docs for this new config option



Discussion:

Resolving patch:
diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py
--- a/elasticapm/conf/__init__.py
+++ b/elasticapm/conf/__init__.py
@@ -517,6 +517,7 @@ class Config(_ConfigBase):
     server_url = _ConfigValue("SERVER_URL", default="http://localhost:8200", required=True)
     server_cert = _ConfigValue("SERVER_CERT", validators=[FileIsReadableValidator()])
     verify_server_cert = _BoolConfigValue("VERIFY_SERVER_CERT", default=True)
+    use_certifi = _BoolConfigValue("USE_CERTIFI", default=True)
     include_paths = _ListConfigValue("INCLUDE_PATHS")
     exclude_paths = _ListConfigValue("EXCLUDE_PATHS", default=compat.get_default_library_patters())
     filter_exception_types = _ListConfigValue("FILTER_EXCEPTION_TYPES")
diff --git a/elasticapm/transport/http.py b/elasticapm/transport/http.py
--- a/elasticapm/transport/http.py
+++ b/elasticapm/transport/http.py
@@ -55,8 +55,7 @@ class Transport(HTTPTransportBase):
     def __init__(self, url, *args, **kwargs):
         super(Transport, self).__init__(url, *args, **kwargs)
         url_parts = compat.urlparse.urlparse(url)
-        ca_certs = certifi.where() if certifi else None
-        pool_kwargs = {"cert_reqs": "CERT_REQUIRED", "ca_certs": ca_certs, "block": True}
+        pool_kwargs = {"cert_reqs": "CERT_REQUIRED", "ca_certs": self.ca_certs, "block": True}
         if self._server_cert and url_parts.scheme != "http":
             pool_kwargs.update(
                 {"assert_fingerprint": self.cert_fingerprint, "assert_hostname": False, "cert_reqs": ssl.CERT_NONE}
@@ -184,6 +183,14 @@ def auth_headers(self):
         headers = super(Transport, self).auth_headers
         return {k.encode("ascii"): v.encode("ascii") for k, v in compat.iteritems(headers)}
 
+    @property
+    def ca_certs(self):
+        """
+        Return location of certificate store. If it is available and not disabled via setting,
+        this will return the location of the certifi certificate store.
+        """
+        return certifi.where() if (certifi and self.client.config.use_certifi) else None
+
 
 # left for backwards compatibility
 AsyncTransport = Transport


Test patch:
diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py
--- a/tests/transports/test_urllib3.py
+++ b/tests/transports/test_urllib3.py
@@ -31,6 +31,7 @@
 
 import os
 
+import certifi
 import mock
 import pytest
 import urllib3.poolmanager
@@ -100,40 +101,40 @@ def test_generic_error(mock_urlopen, elasticapm_client):
         transport.close()
 
 
-def test_http_proxy_environment_variable():
+def test_http_proxy_environment_variable(elasticapm_client):
     with mock.patch.dict("os.environ", {"HTTP_PROXY": "http://example.com"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert isinstance(transport.http, urllib3.ProxyManager)
 
 
-def test_https_proxy_environment_variable():
+def test_https_proxy_environment_variable(elasticapm_client):
     with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert isinstance(transport.http, urllib3.poolmanager.ProxyManager)
 
 
-def test_https_proxy_environment_variable_is_preferred():
+def test_https_proxy_environment_variable_is_preferred(elasticapm_client):
     with mock.patch.dict("os.environ", {"https_proxy": "https://example.com", "HTTP_PROXY": "http://example.com"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert isinstance(transport.http, urllib3.poolmanager.ProxyManager)
         assert transport.http.proxy.scheme == "https"
 
 
-def test_no_proxy_star():
+def test_no_proxy_star(elasticapm_client):
     with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager)
 
 
-def test_no_proxy_host():
+def test_no_proxy_host(elasticapm_client):
     with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "localhost"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager)
 
 
-def test_no_proxy_all():
+def test_no_proxy_all(elasticapm_client):
     with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}):
-        transport = Transport("http://localhost:9999", client=None)
+        transport = Transport("http://localhost:9999", client=elasticapm_client)
         assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager)
 
 
@@ -270,8 +271,8 @@ def test_ssl_cert_pinning_fails(waiting_httpsserver, elasticapm_client):
     assert "Fingerprints did not match" in exc_info.value.args[0]
 
 
-def test_config_url():
-    transport = Transport("http://example.com/" + constants.EVENTS_API_PATH, client=None)
+def test_config_url(elasticapm_client):
+    transport = Transport("http://example.com/" + constants.EVENTS_API_PATH, client=elasticapm_client)
     assert transport._config_url == "http://example.com/" + constants.AGENT_CONFIG_PATH
 
 
@@ -346,3 +347,10 @@ def test_get_config_empty_response(waiting_httpserver, caplog, elasticapm_client
     assert max_age == 5
     record = caplog.records[-1]
     assert record.message == "APM Server answered with empty body and status code 200"
+
+
+def test_use_certifi(elasticapm_client):
+    transport = Transport("/" + constants.EVENTS_API_PATH, client=elasticapm_client)
+    assert transport.ca_certs == certifi.where()
+    elasticapm_client.config.update("2", use_certifi=False)
+    assert not transport.ca_certs