Skip to content

Commit 72cfb19

Browse files
beniwohlibasepi
andauthored
implement server_ca_cert_file config option (#1852)
* implement server_ca_cert_file config option * update changelog * revert PEM fingerprint this was changed during local development and was committed mistakenly --------- Co-authored-by: Colton Myers <[email protected]>
1 parent e5dda56 commit 72cfb19

File tree

8 files changed

+57
-8
lines changed

8 files changed

+57
-8
lines changed

CHANGELOG.asciidoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ endif::[]
2929
//===== Bug fixes
3030
//
3131
32+
=== Unreleased
33+
34+
// Unreleased changes go here
35+
// When the next release happens, nest these changes under the "Python Agent version 6.x" heading
36+
[float]
37+
==== Features
38+
* Add `server_ca_cert_file` option to provide custom CA certificate {pull}1852[#1852]
39+
40+
// [float]
41+
// ===== Bug fixes
42+
3243
3344
[[release-notes-6.x]]
3445
=== Python Agent version 6.x

docs/configuration.asciidoc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,6 @@ By default, the agent verifies the SSL certificate if an HTTPS connection to the
10071007
Verification can be disabled by changing this setting to `False`.
10081008
This setting is ignored when <<config-server-cert,`server_cert`>> is set.
10091009

1010-
NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python 3.4.3+.
1011-
10121010
[float]
10131011
[[config-server-cert]]
10141012
==== `server_cert`
@@ -1019,10 +1017,30 @@ NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python
10191017
| `ELASTIC_APM_SERVER_CERT` | `SERVER_CERT` | `None`
10201018
|============
10211019

1022-
If you have configured your APM Server with a self signed TLS certificate, or you
1020+
If you have configured your APM Server with a self-signed TLS certificate, or you
10231021
just wish to pin the server certificate, you can specify the path to the PEM-encoded
10241022
certificate via the `ELASTIC_APM_SERVER_CERT` configuration.
10251023

1024+
NOTE: If this option is set, the agent only verifies that the certificate provided by the APM Server is
1025+
identical to the one configured here. Validity of the certificate is not checked.
1026+
1027+
[float]
1028+
[[config-server-ca-cert-file]]
1029+
==== `server_ca_cert_file`
1030+
1031+
[options="header"]
1032+
|============
1033+
| Environment | Django/Flask | Default
1034+
| `ELASTIC_APM_SERVER_CA_CERT_FILE` | `SERVER_CA_CERT_FILE` | `None`
1035+
|============
1036+
1037+
By default, the agent will validate the TLS/SSL certificate of the APM Server using the well-known CAs curated by Mozilla,
1038+
and provided by the https://pypi.org/project/certifi/[`certifi`] package.
1039+
1040+
You can set this option to the path of a file containing a CA certificate that will be used instead.
1041+
1042+
Specifying this option is required when using self-signed certificates, unless server certificate validation is disabled.
1043+
10261044
[float]
10271045
[[config-use-certifi]]
10281046
==== `use_certifi`

elasticapm/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def __init__(self, config=None, **inline):
149149
"headers": headers,
150150
"verify_server_cert": self.config.verify_server_cert,
151151
"server_cert": self.config.server_cert,
152+
"server_ca_cert_file": self.config.server_ca_cert_file,
152153
"timeout": self.config.server_timeout,
153154
"processors": self.load_processors(),
154155
}

elasticapm/conf/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ class Config(_ConfigBase):
558558
debug = _BoolConfigValue("DEBUG", default=False)
559559
server_url = _ConfigValue("SERVER_URL", default="http://127.0.0.1:8200", required=True)
560560
server_cert = _ConfigValue("SERVER_CERT", validators=[FileIsReadableValidator()])
561+
server_ca_cert_file = _ConfigValue("SERVER_CA_CERT_FILE", validators=[FileIsReadableValidator()])
561562
verify_server_cert = _BoolConfigValue("VERIFY_SERVER_CERT", default=True)
562563
use_certifi = _BoolConfigValue("USE_CERTIFI", default=True)
563564
include_paths = _ListConfigValue("INCLUDE_PATHS")

elasticapm/transport/http.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,11 @@ def auth_headers(self):
243243
@property
244244
def ca_certs(self):
245245
"""
246-
Return location of certificate store. If it is available and not disabled via setting,
247-
this will return the location of the certifi certificate store.
246+
Return location of certificate store. If the server_ca_cert_file config option is set,
247+
its value is returned. Otherwise, the certifi store is used, unless it is disabled or not installed.
248248
"""
249+
if self._server_ca_cert_file:
250+
return self._server_ca_cert_file
249251
return certifi.where() if (certifi and self.client.config.use_certifi) else None
250252

251253

elasticapm/transport/http_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ def __init__(
4444
headers=None,
4545
timeout=None,
4646
server_cert=None,
47+
server_ca_cert_file=None,
4748
**kwargs
4849
):
4950
self._url = url
5051
self._verify_server_cert = verify_server_cert
5152
self._server_cert = server_cert
53+
self._server_ca_cert_file = server_ca_cert_file
5254
self._timeout = timeout
5355
self._headers = {
5456
k.encode("ascii") if isinstance(k, str) else k: v.encode("ascii") if isinstance(v, str) else v

tests/transports/test_urllib3.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
except ImportError:
4848
from urllib import parse as urlparse
4949

50+
CUR_DIR = os.path.dirname(os.path.realpath(__file__))
51+
5052

5153
@pytest.mark.flaky(reruns=3) # test is flaky on Windows
5254
def test_send(waiting_httpserver, elasticapm_client):
@@ -237,10 +239,9 @@ def test_ssl_cert_pinning_http(waiting_httpserver, elasticapm_client):
237239
@pytest.mark.flaky(reruns=3) # test is flaky on Windows
238240
def test_ssl_cert_pinning(waiting_httpsserver, elasticapm_client):
239241
waiting_httpsserver.serve_content(code=202, content="", headers={"Location": "https://example.com/foo"})
240-
cur_dir = os.path.dirname(os.path.realpath(__file__))
241242
transport = Transport(
242243
waiting_httpsserver.url,
243-
server_cert=os.path.join(cur_dir, "..", "ca/server.pem"),
244+
server_cert=os.path.join(CUR_DIR, "..", "ca/server.pem"),
244245
verify_server_cert=True,
245246
client=elasticapm_client,
246247
)
@@ -252,6 +253,19 @@ def test_ssl_cert_pinning(waiting_httpsserver, elasticapm_client):
252253
transport.close()
253254

254255

256+
@pytest.mark.parametrize(
257+
"sending_elasticapm_client",
258+
[{"server_ca_cert_file": os.path.normpath(os.path.join(CUR_DIR, "..", "ca/ca.crt"))}],
259+
indirect=True,
260+
)
261+
def test_custom_ca_cert(sending_elasticapm_client):
262+
ca_cert = os.path.normpath(os.path.join(CUR_DIR, "..", "ca/ca.crt"))
263+
pool = sending_elasticapm_client._transport.http
264+
assert pool.connection_pool_kw["ca_certs"] == ca_cert
265+
conn = pool.connection_from_url("https://localhost")
266+
assert conn.ca_certs == ca_cert
267+
268+
255269
@pytest.mark.flaky(reruns=3) # test is flaky on Windows
256270
def test_ssl_cert_pinning_fails(waiting_httpsserver, elasticapm_client):
257271
waiting_httpsserver.serve_content(code=202, content="", headers={"Location": "https://example.com/foo"})

tests/utils/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def test_url_to_destination_bad_port():
233233
def test_read_pem_file():
234234
with open(os.path.join(os.path.dirname(__file__), "..", "ca", "ca.crt"), mode="rb") as f:
235235
result = read_pem_file(f)
236-
assert result.startswith(b"0\x82\x05{0\x82\x03c\xa0\x03\x02\x01\x02\x02\x14")
236+
assert result.startswith(b"0\x82\x05{0\x82\x03c\xa0\x03\x02\x01\x02\x02\x14`c\xd8:\xe7")
237237

238238

239239
def test_read_pem_file_chain():

0 commit comments

Comments
 (0)