|
@@ -1,9 +1,15 @@
|
|
-from unittest.mock import patch
|
|
|
|
|
|
+import ipaddress
|
|
|
|
+from unittest.mock import MagicMock, patch
|
|
|
|
|
|
from django.test import override_settings
|
|
from django.test import override_settings
|
|
|
|
+from pytest import raises
|
|
from requests import Request
|
|
from requests import Request
|
|
|
|
|
|
-from sentry.shared_integrations.client.proxy import IntegrationProxyClient
|
|
|
|
|
|
+from sentry.shared_integrations.client.proxy import (
|
|
|
|
+ IntegrationProxyClient,
|
|
|
|
+ get_control_silo_ip_address,
|
|
|
|
+)
|
|
|
|
+from sentry.shared_integrations.exceptions import ApiHostError
|
|
from sentry.silo.base import SiloMode
|
|
from sentry.silo.base import SiloMode
|
|
from sentry.silo.util import PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER
|
|
from sentry.silo.util import PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER
|
|
from sentry.testutils.cases import TestCase
|
|
from sentry.testutils.cases import TestCase
|
|
@@ -84,3 +90,101 @@ class IntegrationProxyClientTest(TestCase):
|
|
assert prepared_request.url != raw_url
|
|
assert prepared_request.url != raw_url
|
|
for header in [PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER]:
|
|
for header in [PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER]:
|
|
assert header in prepared_request.headers
|
|
assert header in prepared_request.headers
|
|
|
|
+
|
|
|
|
+ @override_settings(SILO_MODE=SiloMode.REGION)
|
|
|
|
+ @patch("sentry.shared_integrations.client.proxy.get_control_silo_ip_address")
|
|
|
|
+ @patch("socket.getaddrinfo")
|
|
|
|
+ def test_invalid_control_silo_ip_address(
|
|
|
|
+ self, mock_getaddrinfo, mock_get_control_silo_ip_address
|
|
|
|
+ ):
|
|
|
|
+ with patch("sentry_sdk.capture_exception") as mock_capture_exception, raises(ApiHostError):
|
|
|
|
+ mock_get_control_silo_ip_address.return_value = ipaddress.ip_address("127.0.0.1")
|
|
|
|
+ mock_getaddrinfo.return_value = [(2, 1, 6, "", ("172.31.255.255", 0))]
|
|
|
|
+ client = self.client_cls(org_integration_id=self.oi_id)
|
|
|
|
+
|
|
|
|
+ client.get(f"{self.base_url}/some/endpoint", params={"query": 1, "user": "me"})
|
|
|
|
+
|
|
|
|
+ assert mock_capture_exception.call_count == 1
|
|
|
|
+ err = mock_capture_exception.call_args.args[0]
|
|
|
|
+ assert err.args == ("Disallowed Control Silo IP address: 172.31.255.255",)
|
|
|
|
+
|
|
|
|
+ @override_settings(SILO_MODE=SiloMode.REGION)
|
|
|
|
+ @patch("sentry.shared_integrations.client.proxy.is_control_silo_ip_address")
|
|
|
|
+ @patch("sentry.shared_integrations.client.proxy.get_control_silo_ip_address")
|
|
|
|
+ @patch("socket.getaddrinfo")
|
|
|
|
+ def test_valid_control_silo_ip_address(
|
|
|
|
+ self, mock_getaddrinfo, mock_get_control_silo_ip_address, mock_is_control_silo_ip_address
|
|
|
|
+ ):
|
|
|
|
+ mock_get_control_silo_ip_address.return_value = ipaddress.ip_address("172.31.255.255")
|
|
|
|
+ mock_getaddrinfo.return_value = [(2, 1, 6, "", ("172.31.255.255", 0))]
|
|
|
|
+ client = self.client_cls(org_integration_id=self.oi_id)
|
|
|
|
+
|
|
|
|
+ class BailOut(Exception):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def test_is_control_silo_ip_address(ip):
|
|
|
|
+ assert ip == "172.31.255.255"
|
|
|
|
+ # We can't use responses library for this unit test as it hooks Session.send. So we assert that the
|
|
|
|
+ # is_control_silo_ip_address function is properly called.
|
|
|
|
+ raise BailOut()
|
|
|
|
+
|
|
|
|
+ mock_is_control_silo_ip_address.side_effect = test_is_control_silo_ip_address
|
|
|
|
+
|
|
|
|
+ with raises(BailOut):
|
|
|
|
+ client.get(f"{self.base_url}/some/endpoint", params={"query": 1, "user": "me"})
|
|
|
|
+ assert mock_is_control_silo_ip_address.call_count == 1
|
|
|
|
+
|
|
|
|
+ @override_settings(SILO_MODE=SiloMode.CONTROL)
|
|
|
|
+ @patch("sentry.shared_integrations.client.proxy.is_control_silo_ip_address")
|
|
|
|
+ @patch("sentry.shared_integrations.client.proxy.get_control_silo_ip_address")
|
|
|
|
+ @patch("socket.getaddrinfo")
|
|
|
|
+ def test_does_not_validate_control_silo_ip_address_in_control(
|
|
|
|
+ self, mock_getaddrinfo, mock_get_control_silo_ip_address, mock_is_control_silo_ip_address
|
|
|
|
+ ):
|
|
|
|
+ mock_get_control_silo_ip_address.return_value = ipaddress.ip_address("172.31.255.255")
|
|
|
|
+ mock_getaddrinfo.return_value = [(2, 1, 6, "", ("172.31.255.255", 0))]
|
|
|
|
+ client = self.client_cls(org_integration_id=self.oi_id)
|
|
|
|
+
|
|
|
|
+ class BailOut(Exception):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def test_socket_connection(*args, **kwargs):
|
|
|
|
+ # We can't use responses library for this unit test as it hooks Session.send. So we assert that the
|
|
|
|
+ # socket connection is being opened.
|
|
|
|
+ raise BailOut()
|
|
|
|
+
|
|
|
|
+ with patch("socket.socket") as mock_socket, raises(BailOut):
|
|
|
|
+ mock_socket.side_effect = test_socket_connection
|
|
|
|
+ client.get(f"{self.base_url}/some/endpoint", params={"query": 1, "user": "me"})
|
|
|
|
+
|
|
|
|
+ # Assert control silo ip address was not validated
|
|
|
|
+ assert mock_get_control_silo_ip_address.call_count == 0
|
|
|
|
+ assert mock_is_control_silo_ip_address.call_count == 0
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def test_get_control_silo_ip_address():
|
|
|
|
+ with override_settings(SENTRY_CONTROL_ADDRESS=None):
|
|
|
|
+ assert get_control_silo_ip_address() is None
|
|
|
|
+
|
|
|
|
+ with override_settings(SENTRY_CONTROL_ADDRESS=control_address):
|
|
|
|
+ get_control_silo_ip_address.cache_clear()
|
|
|
|
+ with patch("socket.gethostbyname") as mock_gethostbyname, patch(
|
|
|
|
+ "sentry_sdk.capture_exception"
|
|
|
|
+ ) as mock_capture_exception:
|
|
|
|
+ mock_gethostbyname.return_value = "172.31.255.255"
|
|
|
|
+ assert get_control_silo_ip_address() == ipaddress.ip_address("172.31.255.255")
|
|
|
|
+ assert mock_capture_exception.call_count == 0
|
|
|
|
+
|
|
|
|
+ get_control_silo_ip_address.cache_clear()
|
|
|
|
+ with patch("socket.gethostbyname") as mock_gethostbyname, patch(
|
|
|
|
+ "urllib3.util.parse_url"
|
|
|
|
+ ) as mock_parse_url, patch("sentry_sdk.capture_exception") as mock_capture_exception:
|
|
|
|
+ mock_parse_url.return_value = MagicMock(host=None)
|
|
|
|
+ assert get_control_silo_ip_address() is None
|
|
|
|
+ assert mock_gethostbyname.call_count == 0
|
|
|
|
+ assert mock_capture_exception.call_count == 1
|
|
|
|
+
|
|
|
|
+ err = mock_capture_exception.call_args.args[0]
|
|
|
|
+ assert err.args == (
|
|
|
|
+ f"Unable to parse hostname of control silo address: {control_address}",
|
|
|
|
+ )
|