Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions instsci/http_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@
import logging
import os
import time
import urllib3

import requests

logger = logging.getLogger(__name__)

# Auto-detect local HTTP connector and disable SSL verification warnings.
if os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY") or \
os.environ.get("http_proxy") or os.environ.get("https_proxy"):
# Local network connectors often use self-signed certificates.
_SSL_VERIFY = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
else:
_SSL_VERIFY = True
# TLS verification policy.
#
# Verification stays ON by default -- including behind an HTTP(S) proxy. The
# previous behaviour disabled certificate verification for *every* request as
# soon as any proxy env var was set, which silently exposed authenticated
# institutional sessions and publisher traffic to interception. To trust a
# self-signed connector CA, set REQUESTS_CA_BUNDLE (honoured natively by
# requests). Verification is only disabled by an explicit, deliberate opt-in.
_INSECURE_TLS_ENV = "INSTSCI_INSECURE_TLS"


def _resolve_ssl_verify() -> bool:
opt_in = os.environ.get(_INSECURE_TLS_ENV, "").strip().lower()
if opt_in in {"1", "true", "yes", "on"}:
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
logger.warning(
"%s is set: TLS certificate verification is DISABLED for all HTTP "
"requests. This exposes traffic -- including authenticated sessions "
"-- to interception. Prefer REQUESTS_CA_BUNDLE to trust a "
"self-signed connector CA instead.",
_INSECURE_TLS_ENV,
)
return False
return True


_SSL_VERIFY = _resolve_ssl_verify()


def request_with_retry(
Expand Down
59 changes: 59 additions & 0 deletions tests/test_http_utils_tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import importlib
import os
import unittest

import instsci.http_utils as http_utils

_PROXY_ENV = ("HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy")
_INSECURE_ENV = "INSTSCI_INSECURE_TLS"


class TlsVerifyPolicyTests(unittest.TestCase):
"""TLS verification must stay on unless explicitly opted out.

Regression guard: a proxy env var alone must never disable certificate
verification (which previously exposed authenticated sessions).
"""

def setUp(self):
self._saved = {k: os.environ.get(k) for k in (*_PROXY_ENV, _INSECURE_ENV)}
for k in self._saved:
os.environ.pop(k, None)

def tearDown(self):
for k, v in self._saved.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
importlib.reload(http_utils)

def _verify(self):
return importlib.reload(http_utils)._SSL_VERIFY

def test_verify_on_by_default(self):
self.assertTrue(self._verify())

def test_verify_stays_on_behind_proxy(self):
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:8888"
self.assertTrue(self._verify())

def test_verify_disabled_only_with_explicit_optin(self):
os.environ[_INSECURE_ENV] = "1"
self.assertFalse(self._verify())

def test_optin_accepts_common_truthy_values(self):
for val in ("true", "YES", "on"):
with self.subTest(val=val):
os.environ[_INSECURE_ENV] = val
self.assertFalse(self._verify())

def test_optin_ignores_falsey_values(self):
for val in ("", "0", "false", "no"):
with self.subTest(val=val):
os.environ[_INSECURE_ENV] = val
self.assertTrue(self._verify())


if __name__ == "__main__":
unittest.main()