diff --git a/README.md b/README.md index 95db9eb..a79c6c3 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ DEFAULT_TO_DATE=2026-01-31 | `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. | | `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). | | `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). Если не задана, а `DEFAULT_FROM_DATE` задана — используется сегодняшняя дата. | -| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` / `false` / путь к CA bundle. | +| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` (по умолчанию — стандартная проверка средствами requests) / `false` / путь к CA bundle. Семантика совпадает с `redmine.verify_ssl` в YAML. | ## Использование diff --git a/docs/CONFIG.md b/docs/CONFIG.md index d181886..d057356 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -65,6 +65,22 @@ email: attach: true ``` +### `redmine.verify_ssl` — проверка TLS-сертификата + +Управляет проверкой TLS-сертификата Redmine. Семантика едина с переменной +окружения `REDMINE_VERIFY`: + +| Значение | Поведение | +|---|---| +| `true` (по умолчанию) | Стандартная проверка TLS средствами requests (системные CA / certifi) | +| `false` | Проверка отключена — при запуске выводится предупреждение о риске MITM | +| строка с путём, например `/etc/ssl/my-ca.pem` | Путь к собственному CA-bundle, передаётся в requests как есть | + +До версии с унификацией `verify_ssl: true` подставлял захардкоженный путь +`/etc/ssl/certs/ca-certificates.crt`, который отсутствует на части +дистрибутивов. Теперь `true` в YAML и `REDMINE_VERIFY=true` в env работают +одинаково — оба включают стандартную проверку без привязки к конкретному пути. + ### `period.precision` — точность периода Определяет, как вычисляется следующий период после фиксации: @@ -422,7 +438,7 @@ DEFAULT_TO_DATE=2026-06-30 ``` Если ни `.env`, ни YAML не заданы — используются кодовые дефолты (текущий месяц -как период, стандартный путь сертификатов, пустой автор). +как период, стандартная проверка TLS, пустой автор). ## Безопасность diff --git a/redmine_reporter/config.py b/redmine_reporter/config.py index b41e9d6..7d5898b 100644 --- a/redmine_reporter/config.py +++ b/redmine_reporter/config.py @@ -15,7 +15,7 @@ load_dotenv(override=False) logger = logging.getLogger(__name__) -DEFAULT_REDMINE_VERIFY = "/etc/ssl/certs/ca-certificates.crt" +DEFAULT_REDMINE_VERIFY: Union[bool, str] = True FALSE_VALUES = {"0", "false", "no", "off"} TRUE_VALUES = {"1", "true", "yes", "on"} @@ -157,17 +157,23 @@ class AppConfig: @classmethod def _resolve_verify(cls, raw: dict) -> Union[bool, str]: + """Семантика verify_ssl, единая с REDMINE_VERIFY из env: + + - true (bool или строка) → True — стандартная проверка TLS (requests); + - false (bool или строка) → False — проверка отключена; + - любая другая строка → путь к CA-bundle как есть. + """ value = raw.get("redmine", {}).get("verify_ssl") if value is None: return DEFAULT_REDMINE_VERIFY if isinstance(value, bool): - return DEFAULT_REDMINE_VERIFY if value else False + return value if isinstance(value, str): normalized = value.lower() if normalized in FALSE_VALUES: return False if normalized in TRUE_VALUES: - return DEFAULT_REDMINE_VERIFY + return True return resolve_env_vars(value) return DEFAULT_REDMINE_VERIFY diff --git a/tests/test_config.py b/tests/test_config.py index 079b6aa..9e2a328 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,12 +5,7 @@ from unittest import mock import pytest -from redmine_reporter.config import ( - DEFAULT_REDMINE_VERIFY, - AppConfig, - Config, - EmailConfig, -) +from redmine_reporter.config import AppConfig, Config, EmailConfig @mock.patch.dict( @@ -106,7 +101,9 @@ def test_get_default_date_range_fallback(): @mock.patch.dict(os.environ, {}, clear=True) def test_get_redmine_verify_default(): - assert Config.get_redmine_verify() == DEFAULT_REDMINE_VERIFY + """Без явной настройки проверка TLS включена (стандартные CA requests).""" + Config._app = None + assert Config.get_redmine_verify() is True @pytest.mark.parametrize("value", ["0", "false", "False", "no", "off"]) @@ -355,16 +352,57 @@ class TestAppConfigFromYaml: assert cfg.redmine_api_key == "secret-token" - def test_verify_ssl_true_returns_default_ca_path(self): - """verify_ssl: true → DEFAULT_REDMINE_VERIFY (путь), не Python True.""" + def test_verify_ssl_true_returns_true(self): + """verify_ssl: true → bool True (стандартная проверка TLS).""" with tempfile.TemporaryDirectory() as tmp: yaml_path = Path(tmp) / "config.yml" yaml_path.write_text("redmine:\n verify_ssl: true\n") cfg = AppConfig.from_yaml(yaml_path) - assert cfg.redmine_verify == DEFAULT_REDMINE_VERIFY - assert cfg.redmine_verify is not True # не бул! + assert cfg.redmine_verify is True + + @pytest.mark.parametrize("value", ["true", "True", "yes", "on", "1"]) + def test_verify_ssl_true_string_returns_true(self, value): + """Строковые true-значения в YAML → bool True, как и из env.""" + with tempfile.TemporaryDirectory() as tmp: + yaml_path = Path(tmp) / "config.yml" + yaml_path.write_text(f"redmine:\n verify_ssl: '{value}'\n") + + cfg = AppConfig.from_yaml(yaml_path) + + assert cfg.redmine_verify is True + + def test_verify_ssl_custom_path_returns_path(self): + """Явный путь к CA-bundle в YAML передаётся как есть.""" + with tempfile.TemporaryDirectory() as tmp: + yaml_path = Path(tmp) / "config.yml" + yaml_path.write_text("redmine:\n verify_ssl: /etc/ssl/my-ca.pem\n") + + cfg = AppConfig.from_yaml(yaml_path) + + assert cfg.redmine_verify == "/etc/ssl/my-ca.pem" + + @pytest.mark.parametrize("value", ["false", "False", "no", "off", "0"]) + def test_verify_ssl_false_string_returns_false(self, value): + """Строковые false-значения в YAML → bool False.""" + with tempfile.TemporaryDirectory() as tmp: + yaml_path = Path(tmp) / "config.yml" + yaml_path.write_text(f"redmine:\n verify_ssl: '{value}'\n") + + cfg = AppConfig.from_yaml(yaml_path) + + assert cfg.redmine_verify is False + + def test_verify_ssl_missing_returns_true(self): + """Без verify_ssl в YAML — проверка TLS включена (True).""" + with tempfile.TemporaryDirectory() as tmp: + yaml_path = Path(tmp) / "config.yml" + yaml_path.write_text("redmine:\n url: https://x.com/\n") + + cfg = AppConfig.from_yaml(yaml_path) + + assert cfg.redmine_verify is True def test_verify_ssl_false_returns_false(self): with tempfile.TemporaryDirectory() as tmp: