fix: unify verify_ssl semantics across config sources
YAML verify_ssl: true раньше подставлял захардкоженный путь /etc/ssl/certs/ca-certificates.crt, а REDMINE_VERIFY=true — bool True. Путь отсутствует на части дистрибутивов, семантика источников различалась. Теперь едино для YAML и env: - true (bool/строка) → True: стандартная проверка TLS средствами requests; - false (bool/строка) → False (+ сохраняется warning из #57); - иная строка → путь к CA-bundle как есть. - дефолт (значение не задано) → True вместо захардкоженного пути. Существующие тесты на путь-от-true переписаны под новую семантику (изменение поведения): test_verify_ssl_true_returns_default_ca_path → test_verify_ssl_true_returns_true. Closes #62
This commit is contained in:
@@ -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. |
|
||||
|
||||
## Использование
|
||||
|
||||
|
||||
@@ -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, пустой автор).
|
||||
|
||||
## Безопасность
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user