Skip to content

feat: Add mTLS configuration via x.509 for asynchronous session in google-auth#1958

Open
agrawalradhika-cell wants to merge 7 commits intomainfrom
add-async-mtls
Open

feat: Add mTLS configuration via x.509 for asynchronous session in google-auth#1958
agrawalradhika-cell wants to merge 7 commits intomainfrom
add-async-mtls

Conversation

@agrawalradhika-cell
Copy link
Contributor

This pull request introduces support for Mutual TLS (mTLS) in the asynchronous transport layer of the google-auth library. It enables AsyncAuthorizedSession to automatically discover and utilize client certificates for secure communication with Google Cloud APIs. See go/caa:x509-async-support for details.

Please note: Only x.509 creds are in scope of this project currently. Context aware or ECP credentials are not in scope of this project currently.

This PR is second part of #1956

Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
… handling async and removing encrypted_key complication

Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
@agrawalradhika-cell agrawalradhika-cell requested review from a team as code owners February 13, 2026 23:57
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @agrawalradhika-cell, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the google-auth library by integrating Mutual TLS (mTLS) capabilities into its asynchronous transport layer. It allows AsyncAuthorizedSession to automatically discover and apply x.509 client certificates, ensuring more secure and authenticated communication with Google Cloud APIs. This change is crucial for environments requiring strong client identity verification, providing a robust mechanism for secure asynchronous API interactions.

Highlights

  • Asynchronous mTLS Support: Introduced a new mtls.py module containing helper functions to facilitate Mutual TLS (mTLS) for asynchronous operations, including client certificate discovery and SSL context creation.
  • AsyncAuthorizedSession mTLS Configuration: The AsyncAuthorizedSession now includes a configure_mtls_channel method, enabling it to automatically discover and utilize x.509 client certificates for secure communication.
  • Dynamic SSLContext Reconfiguration: When mTLS is enabled, the AsyncAuthorizedSession dynamically reconfigures its underlying aiohttp.ClientSession with a new ssl.SSLContext loaded with the client certificate and key.
  • Non-blocking Certificate Operations: Implemented _run_in_executor to ensure that blocking file I/O operations related to certificate loading are performed off the main event loop, maintaining asynchronous responsiveness.
  • Comprehensive Test Coverage: Added new test suites (test_sessions_mtls.py and test_aio_mtls_helper.py) to validate the correct behavior of mTLS configuration and helper functions in asynchronous contexts.
Changelog
  • google/auth/aio/transport/mtls.py
    • Added _create_temp_file context manager for secure temporary file handling.
    • Added make_client_cert_ssl_context to create an SSLContext from certificate and key bytes.
    • Added _check_config_path to validate and expand certificate configuration file paths.
    • Added _run_in_executor to offload blocking operations to a thread pool.
    • Added has_default_client_cert_source to check for the presence of default client SSL credentials.
    • Added default_client_cert_source to provide a callback for default client SSL credentials.
    • Added get_client_ssl_credentials to retrieve client certificate, key, and passphrase.
    • Added get_client_cert_and_key to obtain client certificate and key, supporting both callbacks and default sources.
  • google/auth/aio/transport/sessions.py
    • Imported mtls module and aiohttp.
    • Initialized _is_mtls and _cached_Cert attributes in AsyncAuthorizedSession's constructor.
    • Added configure_mtls_channel asynchronous method to AsyncAuthorizedSession for mTLS setup.
    • Modified request method to initialize headers to an empty dictionary if None.
    • Added is_mtls property to AsyncAuthorizedSession to indicate mTLS status.
  • tests/transport/aio/test_sessions_mtls.py
    • Added new test file for AsyncAuthorizedSession mTLS functionality.
    • Added test_configure_mtls_channel to verify successful mTLS configuration.
    • Added test_configure_mtls_channel_disabled to test scenarios where mTLS is not enabled.
    • Added test_configure_mtls_channel_invalid_format to ensure proper error handling for malformed configurations.
    • Added test_configure_mtls_channel_mock_callback to test mTLS configuration using a custom callback.
  • tests/transport/test_aio_mtls_helper.py
    • Added new test file for google.auth.aio.transport.mtls helper functions.
    • Added tests for _check_config_path covering existing and non-existent paths.
    • Added tests for has_default_client_cert_source including environment variable and default path checks.
    • Added tests for default_client_cert_source covering success, not found, and exception wrapping.
    • Added tests for get_client_ssl_credentials covering success and error scenarios.
    • Added tests for get_client_cert_and_key covering callback (async/sync) and default credential retrieval, and exception propagation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

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 introduces mTLS support for asynchronous sessions in google-auth. However, a security misconfiguration exists where the is_mtls property can return True even if mTLS was not successfully applied to a custom transport. Additionally, there's a potential NameError if aiohttp is not installed, a typo in an attribute name, and an efficiency issue where a callback is executed twice. Addressing these will improve the robustness, security, and performance of the mTLS support.

if not _auth_request and AIOHTTP_INSTALLED:
_auth_request = AiohttpRequest()
self._is_mtls = False
self._cached_Cert = None
Copy link
Contributor

Choose a reason for hiding this comment

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

high

There is a typo in the attribute name here (capital 'C'). It should be _cached_cert to match its usage in configure_mtls_channel at line 174.

        self._cached_cert = None

Comment on lines +168 to +184
self._is_mtls,
cert,
key,
) = await mtls.get_client_cert_and_key(client_cert_callback)

if self._is_mtls:
self._cached_cert = cert
ssl_context = await mtls._run_in_executor(
mtls.make_client_cert_ssl_context, cert, key
)

# Re-create the auth request with the new SSL context
if isinstance(self._auth_request, AiohttpRequest):
connector = aiohttp.TCPConnector(ssl=ssl_context)
new_session = aiohttp.ClientSession(connector=connector)
await self._auth_request.close()
self._auth_request = AiohttpRequest(session=new_session)
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The is_mtls property can return a misleading True value when a custom transport is used. The self._is_mtls attribute is set to True at line 168, but the actual application of mTLS to the transport only occurs if the transport is an instance of AiohttpRequest (lines 180-184). If a user provides a custom auth_request that is not an AiohttpRequest, the transport remains unconfigured for mTLS, yet session.is_mtls will return True. This misleading state could lead users to believe their connection is protected by mTLS when it is not. Furthermore, if aiohttp is not installed, AiohttpRequest will not be defined, leading to a NameError when the isinstance check is performed at line 180. Recommendation: Only set self._is_mtls = True if the mTLS configuration was successfully applied to the transport, and ensure AIOHTTP_INSTALLED is checked before performing the isinstance check to prevent a NameError.

                if AIOHTTP_INSTALLED and isinstance(self._auth_request, AiohttpRequest):

Comment on lines +218 to +223
try:
# If it's awaitable, this works.
cert, key = await client_cert_callback()
except TypeError:
# If it's not awaitable (e.g., a tuple), result is already the data.
cert, key = client_cert_callback()
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation calls client_cert_callback() twice if it is a synchronous function: once in the try block (which raises a TypeError when awaited) and again in the except block. This can be inefficient or problematic if the callback has side effects. Consider checking if the result is a coroutine before deciding whether to await it.

Suggested change
try:
# If it's awaitable, this works.
cert, key = await client_cert_callback()
except TypeError:
# If it's not awaitable (e.g., a tuple), result is already the data.
cert, key = client_cert_callback()
result = client_cert_callback()
if asyncio.iscoroutine(result):
cert, key = await result
else:
cert, key = result

Comment on lines +143 to +144
google.auth.exceptions.DefaultClientCertSourceError: If the default
client SSL credentials don't exist or are malformed.
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The docstring references google.auth.exceptions.DefaultClientCertSourceError, but this exception does not exist in google/auth/exceptions.py. The code actually raises MutualTLSChannelError.

Comment on lines +46 to +54
fd, path = tempfile.mkstemp()
try:
with os.fdopen(fd, "wb") as f:
f.write(content)
yield path
finally:
# Securely delete the file after use.
if os.path.exists(path):
os.remove(path)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The variable name path shadows the path module imported from os at line 23. While it doesn't break the code here because of how os.path is used later, it's better to use a different name like file_path to avoid confusion and maintain clarity.

Suggested change
fd, path = tempfile.mkstemp()
try:
with os.fdopen(fd, "wb") as f:
f.write(content)
yield path
finally:
# Securely delete the file after use.
if os.path.exists(path):
os.remove(path)
fd, file_path = tempfile.mkstemp()
try:
with os.fdopen(fd, "wb") as f:
f.write(content)
yield file_path
finally:
# Securely delete the file after use.
if os.path.exists(file_path):
os.remove(file_path)

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