From 0b9ff321cd8a82f9198b27b1ed9decd3e7d49935 Mon Sep 17 00:00:00 2001 From: wfealdel Date: Mon, 24 Aug 2026 16:00:26 -0400 Subject: [PATCH] [AAP-76188]fix: enforce TLS certificate validation for JWT and resource server Change ANSIBLE_BASE_JWT_VALIDATE_CERT and RESOURCE_SERVER__VALIDATE_HTTPS defaults from False to True in production settings to prevent JWT token forgery and authentication bypass via MITM attacks. Development mode retains False defaults via development_defaults.py. A startup warning is logged when either setting is disabled in non-debug mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/deployment.md | 37 ++++++++++++++++++++ src/aap_eda/settings/defaults.py | 6 ++-- src/aap_eda/settings/post_load.py | 25 ++++++++++++++ src/aap_eda/utils/logging.py | 1 + tests/unit/test_settings.py | 57 +++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 3 deletions(-) diff --git a/docs/deployment.md b/docs/deployment.md index 2a87de6a1..36374ca33 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -56,6 +56,43 @@ You might want to configure the AWX integration. To do so, you need to set the e export EDA_CONTROLLER_SSL_VERIFY=yes ``` +#### Configure TLS Certificate Verification + +EDA enforces TLS certificate verification by default for both JWT key +endpoints and resource server communication. In production deployments +(the default mode), the following settings default to `True`: + +- `EDA_ANSIBLE_BASE_JWT_VALIDATE_CERT` - Validates certificates when + fetching JWT signing keys. +- `EDA_RESOURCE_SERVER__VALIDATE_HTTPS` - Validates certificates when + communicating with the AAP Gateway resource server. + +If your deployment uses certificates signed by a private CA, configure +the system CA bundle rather than disabling verification: + +1. Add your CA certificate to the system trust store: + + ```bash + cp your-ca.crt /etc/pki/ca-trust/source/anchors/ + update-ca-trust + ``` + +2. Alternatively, set the `REQUESTS_CA_BUNDLE` environment variable to + point to your CA bundle file: + + ```bash + export REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt + ``` + +**Warning:** Disabling TLS verification (`EDA_ANSIBLE_BASE_JWT_VALIDATE_CERT=False` +or `EDA_RESOURCE_SERVER__VALIDATE_HTTPS=False`) removes protection against +man-in-the-middle attacks and should never be done in production. If either +setting is disabled, the EDA server logs a prominent security warning at +startup. + +For development environments, set `EDA_MODE=development` which defaults +both settings to `False` to accommodate self-signed certificates. + ```shell cd tools/docker ``` diff --git a/src/aap_eda/settings/defaults.py b/src/aap_eda/settings/defaults.py index 754921f8e..34824c07e 100644 --- a/src/aap_eda/settings/defaults.py +++ b/src/aap_eda/settings/defaults.py @@ -76,7 +76,7 @@ To configure a Resource Server for syncing of managed resources: * RESOURCE_SERVER__URL - The URL to connect to the resource server * RESOURCE_SERVER__SECRET_KEY - The secret key needed to pull the resource list -* RESOURCE_SERVER__VALIDATE_HTTPS - Whether to validate https, default to False +* RESOURCE_SERVER__VALIDATE_HTTPS - Whether to validate https, default to True * ANSIBLE_BASE_MANAGED_ROLE_REGISTRY - Syncing of the Platform Auditor role """ @@ -194,7 +194,7 @@ # --------------------------------------------------------- # DJANGO ANSIBLE BASE JWT SETTINGS # --------------------------------------------------------- -ANSIBLE_BASE_JWT_VALIDATE_CERT: bool = False +ANSIBLE_BASE_JWT_VALIDATE_CERT: bool = True ANSIBLE_BASE_JWT_KEY: str = "https://localhost" # Default Not allow local resource management. @@ -216,7 +216,7 @@ RESOURCE_SERVER__URL: Optional[str] = "https://localhost" RESOURCE_SERVER__SECRET_KEY: Optional[str] = "" -RESOURCE_SERVER__VALIDATE_HTTPS: bool = False +RESOURCE_SERVER__VALIDATE_HTTPS: bool = True RESOURCE_JWT_USER_ID: Optional[str] = None # The preload data scrip is used instead of the DAB managed role creator diff --git a/src/aap_eda/settings/post_load.py b/src/aap_eda/settings/post_load.py index 118527795..c8ed2dfda 100644 --- a/src/aap_eda/settings/post_load.py +++ b/src/aap_eda/settings/post_load.py @@ -48,6 +48,29 @@ def _normalize_queue_name(name: str) -> str: return normalized +def _warn_tls_verification_disabled(settings: Dynaconf) -> None: + if settings.get("DEBUG", False): + return + + if not settings.get("ANSIBLE_BASE_JWT_VALIDATE_CERT", True): + logger.warning( + "SECURITY WARNING: ANSIBLE_BASE_JWT_VALIDATE_CERT is False. " + "TLS certificate verification is disabled for JWT key " + "endpoints. This is unsafe for production. Set " + "ANSIBLE_BASE_JWT_VALIDATE_CERT=True and configure a " + "CA bundle instead." + ) + + if not settings.get("RESOURCE_SERVER", {}).get("VALIDATE_HTTPS", True): + logger.warning( + "SECURITY WARNING: RESOURCE_SERVER__VALIDATE_HTTPS is False. " + "TLS certificate verification is disabled for resource server " + "communication. This is unsafe for production. Set " + "RESOURCE_SERVER__VALIDATE_HTTPS=True and configure a " + "CA bundle instead." + ) + + def _get_secret_key(settings: Dynaconf) -> str: secret_key = settings.get("SECRET_KEY") secret_key_file = settings.get("SECRET_KEY_FILE") @@ -445,6 +468,8 @@ def post_loading(loaded_settings: Dynaconf): _enforce_types(settings) + _warn_tls_verification_disabled(settings) + # Configure authentication classes based on worker kind # WebSocket workers only need WebsocketJWTAuthentication # for ansible-rulebook workers diff --git a/src/aap_eda/utils/logging.py b/src/aap_eda/utils/logging.py index 1ceefa6fb..04bdba8a8 100644 --- a/src/aap_eda/utils/logging.py +++ b/src/aap_eda/utils/logging.py @@ -67,6 +67,7 @@ def log(self, *args, **kwargs): "ANSIBLE_RULEBOOK_LOG_LEVEL", "ALLOW_LOCAL_RESOURCE_MANAGEMENT", "RESOURCE_JWT_USER_ID", + "ANSIBLE_BASE_JWT_VALIDATE_CERT", "ANSIBLE_BASE_MANAGED_ROLE_REGISTRY", "ACTIVATION_DB_HOST", "SAFE_PLUGINS_FOR_PORT_FORWARD", diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 59df857c9..96d9e4d64 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging + import pytest from django.core.exceptions import ImproperlyConfigured from dynaconf import Dynaconf @@ -233,3 +235,58 @@ def test_non_websocket_worker_authentication_classes(mock_settings): mock_settings.REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"] == expected_classes ) + + +def test_tls_defaults_are_true(mock_settings): + assert mock_settings.ANSIBLE_BASE_JWT_VALIDATE_CERT is True + assert mock_settings.RESOURCE_SERVER__VALIDATE_HTTPS is True + + +def test_tls_disabled_warning_in_production(mock_settings, caplog_factory): + mock_settings.ANSIBLE_BASE_JWT_VALIDATE_CERT = False + mock_settings.RESOURCE_SERVER = { + "URL": "https://localhost", + "SECRET_KEY": "", + "VALIDATE_HTTPS": False, + } + mock_settings.DEBUG = False + + caplog = caplog_factory( + logging.getLogger("aap_eda.settings.post_load"), + level=logging.WARNING, + ) + post_loading(mock_settings) + + assert "ANSIBLE_BASE_JWT_VALIDATE_CERT is False" in caplog.text + assert "RESOURCE_SERVER__VALIDATE_HTTPS is False" in caplog.text + + +def test_tls_disabled_no_warning_in_debug(mock_settings, caplog_factory): + mock_settings.ANSIBLE_BASE_JWT_VALIDATE_CERT = False + mock_settings.RESOURCE_SERVER = { + "URL": "https://localhost", + "SECRET_KEY": "", + "VALIDATE_HTTPS": False, + } + mock_settings.DEBUG = True + + caplog = caplog_factory( + logging.getLogger("aap_eda.settings.post_load"), + level=logging.WARNING, + ) + post_loading(mock_settings) + + assert "ANSIBLE_BASE_JWT_VALIDATE_CERT is False" not in caplog.text + assert "RESOURCE_SERVER__VALIDATE_HTTPS is False" not in caplog.text + + +def test_tls_enabled_no_warning(mock_settings, caplog_factory): + mock_settings.DEBUG = False + + caplog = caplog_factory( + logging.getLogger("aap_eda.settings.post_load"), + level=logging.WARNING, + ) + post_loading(mock_settings) + + assert "SECURITY WARNING" not in caplog.text