From 45480941f88f621a5a8c6bd90fbf7078f0aa4bc6 Mon Sep 17 00:00:00 2001 From: Lila Yasin Date: Fri, 15 May 2026 13:13:15 -0400 Subject: [PATCH] [AAP-53283] Fix analytics API requests to respect proxy environment variables (#16451) Fix analytics API requests to respect proxy environment variables Assisted-by: Claude --- awx/api/views/analytics.py | 51 +++++------ .../tests/functional/api/test_analytics.py | 90 +++++++++++++++++++ 2 files changed, 116 insertions(+), 25 deletions(-) diff --git a/awx/api/views/analytics.py b/awx/api/views/analytics.py index 890307dc7c..1696997804 100644 --- a/awx/api/views/analytics.py +++ b/awx/api/views/analytics.py @@ -9,7 +9,7 @@ from django.utils import translation from awx.api.generics import APIView, Response from awx.api.permissions import AnalyticsPermission from awx.api.versioning import reverse -from awx.main.utils import get_awx_version +from awx.main.utils import get_awx_version, set_environ from awx.main.utils.analytics_proxy import OIDCClient from rest_framework import status @@ -210,31 +210,32 @@ class AnalyticsGenericView(APIView): return self._error_response(ERROR_UNSUPPORTED_METHOD, method, remote=False, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) url = self._get_analytics_url(request.path) using_subscriptions_credentials = False - try: - rh_user = getattr(settings, 'REDHAT_USERNAME', None) - rh_password = getattr(settings, 'REDHAT_PASSWORD', None) - if not (rh_user and rh_password): - rh_user = self._get_setting('SUBSCRIPTIONS_CLIENT_ID', None, ERROR_MISSING_USER) - rh_password = self._get_setting('SUBSCRIPTIONS_CLIENT_SECRET', None, ERROR_MISSING_PASSWORD) - using_subscriptions_credentials = True + with set_environ(**settings.AWX_TASK_ENV): + try: + rh_user = getattr(settings, 'REDHAT_USERNAME', None) + rh_password = getattr(settings, 'REDHAT_PASSWORD', None) + if not (rh_user and rh_password): + rh_user = self._get_setting('SUBSCRIPTIONS_CLIENT_ID', None, ERROR_MISSING_USER) + rh_password = self._get_setting('SUBSCRIPTIONS_CLIENT_SECRET', None, ERROR_MISSING_PASSWORD) + using_subscriptions_credentials = True - client = OIDCClient(rh_user, rh_password) - response = client.make_request( - method, - url, - headers=headers, - verify=settings.INSIGHTS_CERT_PATH, - params=getattr(request, 'query_params', {}), - json=getattr(request, 'data', {}), - timeout=(31, 31), - ) - except requests.RequestException: - # subscriptions credentials are not valid for basic auth, so just return 401 - if using_subscriptions_credentials: - response = Response(status=status.HTTP_401_UNAUTHORIZED) - else: - logger.error("Automation Analytics API request failed, trying base auth method") - response = self._base_auth_request(request, method, url, rh_user, rh_password, headers) + client = OIDCClient(rh_user, rh_password) + response = client.make_request( + method, + url, + headers=headers, + verify=settings.INSIGHTS_CERT_PATH, + params=getattr(request, 'query_params', {}), + json=getattr(request, 'data', {}), + timeout=(31, 31), + ) + except requests.RequestException: + # subscriptions credentials are not valid for basic auth, so just return 401 + if using_subscriptions_credentials: + response = Response(status=status.HTTP_401_UNAUTHORIZED) + else: + logger.error("Automation Analytics API request failed, trying base auth method") + response = self._base_auth_request(request, method, url, rh_user, rh_password, headers) # # Missing or wrong user/pass # diff --git a/awx/main/tests/functional/api/test_analytics.py b/awx/main/tests/functional/api/test_analytics.py index 52fdba74e3..932216c46d 100644 --- a/awx/main/tests/functional/api/test_analytics.py +++ b/awx/main/tests/functional/api/test_analytics.py @@ -1,3 +1,4 @@ +import os import pytest import requests from unittest import mock @@ -257,3 +258,92 @@ class TestAnalyticsGenericView: else: # assert mock_base_auth_request not called mock_base_auth_request.assert_not_called() + + @pytest.mark.django_db + def test__send_to_analytics_respects_proxy_env_oidc(self): + settings_map = { + 'INSIGHTS_TRACKING_STATE': True, + 'AUTOMATION_ANALYTICS_URL': 'https://example.com', + 'REDHAT_USERNAME': 'redhat_user', + 'REDHAT_PASSWORD': 'redhat_pass', + 'SUBSCRIPTIONS_CLIENT_ID': '', + 'SUBSCRIPTIONS_CLIENT_SECRET': '', + 'AWX_TASK_ENV': {'HTTPS_PROXY': '192.168.50.100:1234', 'HTTP_PROXY': '192.168.50.100:5678'}, + } + with override_settings(**settings_map): + request = RequestFactory().post('/some/path') + view = AnalyticsGenericView() + + with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client: + mock_client_instance = mock.Mock() + mock_oidc_client.return_value = mock_client_instance + + def _check_env_and_respond(*args, **kwargs): + assert os.environ.get('HTTPS_PROXY') == '192.168.50.100:1234' + assert os.environ.get('HTTP_PROXY') == '192.168.50.100:5678' + return mock.Mock(status_code=200) + + mock_client_instance.make_request.side_effect = _check_env_and_respond + response = view._send_to_analytics(request, 'POST') + assert response.status_code == 200 + mock_client_instance.make_request.assert_called_once() + + @pytest.mark.django_db + def test__send_to_analytics_respects_proxy_env_basic_auth(self): + settings_map = { + 'INSIGHTS_TRACKING_STATE': True, + 'AUTOMATION_ANALYTICS_URL': 'https://example.com', + 'REDHAT_USERNAME': 'redhat_user', + 'REDHAT_PASSWORD': 'redhat_pass', + 'SUBSCRIPTIONS_CLIENT_ID': '', + 'SUBSCRIPTIONS_CLIENT_SECRET': '', + 'AWX_TASK_ENV': {'HTTPS_PROXY': '192.168.50.100:1234'}, + } + with override_settings(**settings_map): + request = RequestFactory().post('/some/path') + view = AnalyticsGenericView() + + with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client, mock.patch( + 'awx.api.views.analytics.AnalyticsGenericView._base_auth_request' + ) as mock_base_auth: + mock_client_instance = mock.Mock() + mock_oidc_client.return_value = mock_client_instance + mock_client_instance.make_request.side_effect = requests.RequestException("OIDC failed") + + def _check_env_and_respond(*args, **kwargs): + assert os.environ.get('HTTPS_PROXY') == '192.168.50.100:1234' + return mock.Mock(status_code=200) + + mock_base_auth.side_effect = _check_env_and_respond + response = view._send_to_analytics(request, 'POST') + assert response.status_code == 200 + mock_base_auth.assert_called_once() + + @pytest.mark.django_db + def test__send_to_analytics_restores_env_after_request(self): + original_value = os.environ.pop('HTTPS_PROXY', None) + settings_map = { + 'INSIGHTS_TRACKING_STATE': True, + 'AUTOMATION_ANALYTICS_URL': 'https://example.com', + 'REDHAT_USERNAME': 'redhat_user', + 'REDHAT_PASSWORD': 'redhat_pass', + 'SUBSCRIPTIONS_CLIENT_ID': '', + 'SUBSCRIPTIONS_CLIENT_SECRET': '', + 'AWX_TASK_ENV': {'HTTPS_PROXY': '192.168.50.100:1234'}, + } + try: + with override_settings(**settings_map): + request = RequestFactory().post('/some/path') + view = AnalyticsGenericView() + + with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client: + mock_client_instance = mock.Mock() + mock_oidc_client.return_value = mock_client_instance + mock_client_instance.make_request.return_value = mock.Mock(status_code=200) + + view._send_to_analytics(request, 'POST') + + assert 'HTTPS_PROXY' not in os.environ + finally: + if original_value is not None: + os.environ['HTTPS_PROXY'] = original_value