Skip to content

fix(auth): preserve aiohttp session settings when configuring mTLS in AsyncAuthorizedSession - #18692

Draft
agrawalradhika-cell wants to merge 1 commit into
googleapis:mainfrom
agrawalradhika-cell:fix-18314-mtls-session-config
Draft

agrawalradhika-cell wants to merge 1 commit into
googleapis:mainfrom
agrawalradhika-cell:fix-18314-mtls-session-config

Conversation

@agrawalradhika-cell

@agrawalradhika-cell agrawalradhika-cell commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

AsyncAuthorizedSession.configure_mtls_channel() replaces the transport with a new aiohttp.ClientSession/TCPConnector built with default arguments. As a result, any settings on the caller's session are silently dropped: pool limits, headers, cookies, timeout, trust_env, tracing and so on. This happens on the first mTLS setup and again on every cert rotation triggered by a 401.

If the current transport already has a session, the new mTLS session now copies the same settings that aiohttp.Request._clone() copies:

  • connector: limit, limit_per_host, force_close, local_addr
  • session: headers, cookie_jar, auth, timeout, trust_env, auto_decompress, trace_configs, json_serialize

ssl is always set to the new mTLS context. If there is no session yet (the default transport), behavior is unchanged.

Pre-review checklist

  • Scoped to a single logical change: how configure_mtls_channel() builds the new aiohttp session, plus its unit tests. Not part of a stack.
  • Self-reviewed the full diff line by line (go/author-standard).
  • Tested manually / end-to-end against a real environment.
    • Test script and output: pending.
  • Added or updated unit tests covering happy paths and error cases.
    • test_configure_mtls_channel_preserves_existing_session_config: a real ClientSession with every setting customized. All settings survive the first mTLS setup and a second reconfigure (rotation), and each new connector gets the new SSL context.
    • test_configure_mtls_channel_with_minimal_existing_session: a session without a TCPConnector or optional settings. Only ssl, auto_decompress and trust_env are passed.
  • Checked shared code paths for regressions and backwards compatibility.
    • Adjacent features verified: default transport with no session yet (unchanged; the existing test_configure_mtls_channel still asserts the exact calls), 401-triggered cert rotation (settings now survive each rotation), custom non-aiohttp transports (unchanged warning and TLS fallback). The aio tests pass on aiohttp 3.8.0 (Python 3.10) and 3.14.3 (Python 3.13).
  • Checked parity across sibling flows - requests.AuthorizedSession.configure_mtls_channel() already keeps the adapter settings (max_retries, pool sizes, pool_block).
  • Out of scope flows and tracking bugs - urllib3.AuthorizedHttp.configure_mtls_channel() builds a fresh PoolManager(ssl_context=ctx), so the settings of a caller-supplied PoolManager are dropped there too. This could be tracked separately (no issue filed yet).

… AsyncAuthorizedSession

configure_mtls_channel() replaced the transport with a new
aiohttp.ClientSession/TCPConnector built with default arguments, so the
caller's session settings were dropped on the first mTLS setup and on
every certificate rotation triggered by a 401.

When the current transport already has a session, the new mTLS session
now copies the same settings that aiohttp.Request._clone() copies:
connector limit, limit_per_host, force_close and local_addr, and session
headers, cookie_jar, auth, timeout, trust_env, auto_decompress,
trace_configs and json_serialize. The ssl context is always the new mTLS
context. Behavior is unchanged when there is no session yet.

Fixes googleapis#18314

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the mTLS channel configuration to preserve connector and session settings from an existing aiohttp.ClientSession during initial configuration and certificate rotation, and adds corresponding unit tests. The review feedback highlights two important improvements: first, the old session and its connector should be explicitly closed to prevent resource leaks; second, the code should avoid accessing private attributes of aiohttp classes and instead use public properties to reduce maintenance risk.

Comment on lines 378 to 379
old_auth_request = self._auth_request
self._auth_request = AiohttpRequest(session=new_session)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The old session and its underlying connector/connections are not closed when a new session is created during initial configuration or certificate rotation. This will cause resource leaks (unclosed client sessions and connectors) and trigger warnings in aiohttp. Awaiting close() on the old AiohttpRequest resolves this leak.

old_auth_request = self._auth_request
self._auth_request = AiohttpRequest(session=new_session)
if old_auth_request is not None:
    await old_auth_request.close()

Comment on lines +335 to +371
"limit": getattr(
orig_connector, "_limit", 100
),
"limit_per_host": getattr(
orig_connector, "_limit_per_host", 0
),
"force_close": getattr(
orig_connector, "_force_close", False
),
"local_addr": _helpers_async._get_local_addr(
orig_connector
),
}
)
session_kwargs["auto_decompress"] = getattr(
orig_session, "_auto_decompress", True
)
session_kwargs["trust_env"] = getattr(
orig_session, "_trust_env", False
)
trace_configs = getattr(
orig_session, "_trace_configs", None
)
if trace_configs:
session_kwargs["trace_configs"] = list(
trace_configs
)
for attr_name, kwarg_name in [
("_default_headers", "headers"),
("_cookie_jar", "cookie_jar"),
("_default_auth", "auth"),
("_timeout", "timeout"),
("_json_serialize", "json_serialize"),
]:
val = getattr(orig_session, attr_name, None)
if val is not None:
session_kwargs[kwarg_name] = val

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Relying on private attributes (prefixed with _) of aiohttp.ClientSession and aiohttp.TCPConnector is risky because these are internal implementation details that can change across versions without notice. Instead, use the public properties where available. Accessing private fields is bad practice and creates maintenance risk.

"limit": getattr(orig_connector, "limit", 100),
"limit_per_host": getattr(orig_connector, "limit_per_host", 0),
"force_close": getattr(orig_connector, "force_close", False),
"local_addr": _helpers_async._get_local_addr(orig_connector),
}
)
session_kwargs["auto_decompress"] = getattr(orig_session, "auto_decompress", True)
session_kwargs["trust_env"] = getattr(orig_session, "trust_env", False)
trace_configs = getattr(orig_session, "trace_configs", None)
if trace_configs:
    session_kwargs["trace_configs"] = list(trace_configs)
for attr_name, kwarg_name in [
    ("headers", "headers"),
    ("cookie_jar", "cookie_jar"),
    ("timeout", "timeout"),
]:
    val = getattr(orig_session, attr_name, None)
    if val is not None:
        session_kwargs[kwarg_name] = val
References
  1. Avoid accessing private attributes to bypass public API methods, as accessing private fields is bad practice and the minor performance gains do not justify the maintenance risk.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant