Skip to content

Commit 6004832

Browse files
authored
Make server certificate verification mandatory in fips mode (#2227)
* elasticapm/conf: block disabling of verify cert server in fips mode This requires to add validation support to _BoolConfigValue * Send regexp in conf tests as raw strings
1 parent f4040b8 commit 6004832

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

elasticapm/conf/__init__.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import threading
3838
from datetime import timedelta
3939

40+
import _hashlib
41+
4042
from elasticapm.conf.constants import BASE_SANITIZE_FIELD_NAMES, TRACE_CONTINUATION_STRATEGY
4143
from elasticapm.utils import compat, starmatch_to_regex
4244
from elasticapm.utils.logging import get_logger
@@ -220,6 +222,8 @@ class _BoolConfigValue(_ConfigValue):
220222
def __init__(self, dict_key, true_string="true", false_string="false", **kwargs) -> None:
221223
self.true_string = true_string
222224
self.false_string = false_string
225+
# this is necessary to have the bool type preserved in _validate
226+
kwargs["type"] = bool
223227
super(_BoolConfigValue, self).__init__(dict_key, **kwargs)
224228

225229
def __set__(self, instance, value) -> None:
@@ -228,6 +232,7 @@ def __set__(self, instance, value) -> None:
228232
value = True
229233
elif value.lower() == self.false_string:
230234
value = False
235+
value = self._validate(instance, value)
231236
self._callback_if_changed(instance, value)
232237
instance._values[self.dict_key] = bool(value)
233238

@@ -373,6 +378,30 @@ def __call__(self, value, field_name):
373378
return value
374379

375380

381+
def _in_fips_mode():
382+
try:
383+
return _hashlib.get_fips_mode() == 1
384+
except AttributeError:
385+
# versions older of Python3.9 do not have the helper
386+
return False
387+
388+
389+
class SupportedValueInFipsModeValidator(object):
390+
"""If FIPS mode is enabled only supported_value is accepted"""
391+
392+
def __init__(self, supported_value) -> None:
393+
self.supported_value = supported_value
394+
395+
def __call__(self, value, field_name):
396+
if _in_fips_mode():
397+
if value != self.supported_value:
398+
raise ConfigurationError(
399+
"{}={} must be set to {} if FIPS mode is enabled".format(field_name, value, self.supported_value),
400+
field_name,
401+
)
402+
return value
403+
404+
376405
class EnumerationValidator(object):
377406
"""
378407
Validator which ensures that a given config value is chosen from a list
@@ -579,7 +608,9 @@ class Config(_ConfigBase):
579608
server_url = _ConfigValue("SERVER_URL", default="http://127.0.0.1:8200", required=True)
580609
server_cert = _ConfigValue("SERVER_CERT", validators=[FileIsReadableValidator()])
581610
server_ca_cert_file = _ConfigValue("SERVER_CA_CERT_FILE", validators=[FileIsReadableValidator()])
582-
verify_server_cert = _BoolConfigValue("VERIFY_SERVER_CERT", default=True)
611+
verify_server_cert = _BoolConfigValue(
612+
"VERIFY_SERVER_CERT", default=True, validators=[SupportedValueInFipsModeValidator(supported_value=True)]
613+
)
583614
use_certifi = _BoolConfigValue("USE_CERTIFI", default=True)
584615
include_paths = _ListConfigValue("INCLUDE_PATHS")
585616
exclude_paths = _ListConfigValue("EXCLUDE_PATHS", default=compat.get_default_library_patters())

tests/config/tests.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import mock
4040
import pytest
4141

42+
import elasticapm.conf
4243
from elasticapm.conf import (
4344
Config,
4445
ConfigurationError,
@@ -47,6 +48,7 @@
4748
FileIsReadableValidator,
4849
PrecisionValidator,
4950
RegexValidator,
51+
SupportedValueInFipsModeValidator,
5052
UnitValidator,
5153
VersionedConfig,
5254
_BoolConfigValue,
@@ -458,7 +460,7 @@ def test_config_all_upper_case():
458460

459461

460462
def test_regex_validator_without_match():
461-
validator = RegexValidator("\d")
463+
validator = RegexValidator(r"\d")
462464
with pytest.raises(ConfigurationError) as e:
463465
validator("foo", "field")
464466
assert "does not match pattern" in e.value.args[0]
@@ -472,7 +474,7 @@ def test_unit_validator_without_match():
472474

473475

474476
def test_unit_validator_with_unsupported_unit():
475-
validator = UnitValidator("(\d+)(s)", "secs", {})
477+
validator = UnitValidator(r"(\d+)(s)", "secs", {})
476478
with pytest.raises(ConfigurationError) as e:
477479
validator("10s", "field")
478480
assert "is not a supported unit" in e.value.args[0]
@@ -490,3 +492,36 @@ def test_exclude_range_validator_not_in_range():
490492
with pytest.raises(ConfigurationError) as e:
491493
validator(10, "field")
492494
assert "cannot be in range" in e.value.args[0]
495+
496+
497+
def test_supported_value_in_fips_mode_validator_in_fips_mode_with_invalid_value(monkeypatch):
498+
monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: True)
499+
exception_message = "VERIFY_SERVER_CERT=False must be set to True if FIPS mode is enabled"
500+
validator = SupportedValueInFipsModeValidator(supported_value=True)
501+
with pytest.raises(ConfigurationError) as e:
502+
validator(False, "VERIFY_SERVER_CERT")
503+
assert exception_message == e.value.args[0]
504+
505+
config = Config({"VERIFY_SERVER_CERT": False})
506+
assert config.errors["VERIFY_SERVER_CERT"] == exception_message
507+
508+
509+
def test_supported_value_in_fips_mode_validator_in_fips_mode_with_valid_value(monkeypatch):
510+
monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: True)
511+
validator = SupportedValueInFipsModeValidator(supported_value=True)
512+
assert validator(True, "VERIFY_SERVER_CERT") == True
513+
config = Config({"VERIFY_SERVER_CERT": True})
514+
assert config.verify_server_cert == True
515+
assert "VERIFY_SERVER_CERT" not in config.errors
516+
517+
518+
def test_supported_value_in_fips_mode_validator_not_in_fips_mode(monkeypatch):
519+
monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: False)
520+
validator = SupportedValueInFipsModeValidator(supported_value=True)
521+
assert validator(True, "field") == True
522+
assert validator(False, "field") == False
523+
524+
config = Config({"VERIFY_SERVER_CERT": False})
525+
assert not config.errors
526+
config = Config({"VERIFY_SERVER_CERT": True})
527+
assert not config.errors

0 commit comments

Comments
 (0)