-
Notifications
You must be signed in to change notification settings - Fork 346
feat: MDS connections use mTLS #1856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
076c937 to
0d550bc
Compare
8947e48 to
3689ea7
Compare
daniel-sanche
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing major, but left a few suggestions to consider
| Returns: | ||
| google.auth.transport.Request: Request | ||
| object to use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the line break seems unnecessary here
google/auth/compute_engine/_mtls.py
Outdated
| "C:\\", "ProgramData", "Google", "ComputeEngine", "mds-mtls-root.crt" | ||
| ) | ||
| else: | ||
| return os.path.join("/", "run", "google-mds-mtls", "root.crt") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These paths are a bit unweildy, especially since the directory is referenced multiple times in this file. I'd suggest doing something like:
WIN_BASE_PATH_COMPONENTS = ("C:\\", "ProgramData", "Google", "ComputeEngine")
BASE_PATH_COMPONENTS = ("/", "run", "google-mds-mtls")
def _get_mds_root_crt_path():
if os.name == "nt":
return os.path.join(*WIN_BASE_PATH_COMPONENTS, "mds-mtls-root.crt")
else:
return os.path.join(*BASE_PATH_COMPONENTS, "root.crt")
Or, preferably, consider using Path objects instead of strings. It could simplify some of the weirdness of paths on different platforms
from pathlib import Path
WIN_BASE_PATH = PATH("C:/ProgramData/Google/ComputeEngine")
BASE_PATH = PATH("/run/google-mds-mtls")
def _get_mds_root_crt_path():
if os.name == "nt":
return WIN_BASE_PATH / "mds-mtls-root.crt"
else:
return BASE_PATH / "root.crt"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
google/auth/compute_engine/_mtls.py
Outdated
|
|
||
|
|
||
| def _get_mds_root_crt_path(): | ||
| if os.name == "nt": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: The "nt" string is used in a few places, and could be confusing if you're not familiar
Maybe consider making this a constant. Something like WINDOWS_OS_NAME="nt"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this a bit closer, it looks like this change would contradict the public API as it currently stands. Custom request objects constructed and passed in by users would be bypassed after the next release, which could break existing code. I think we need to consider that more before merging
Or let me know if I'm misunterstanding
09c61a6 to
8f7e197
Compare
|
|
||
| if root is None: | ||
| root = _get_metadata_root(use_mtls) | ||
| _validate_gce_mds_configured_environment() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe add a comment explaining what this does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| Raises: | ||
| google.auth.exceptions.TransportError: if an error occurred while | ||
| retrieving metadata. | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raises doesn't mention MutualTLSChannelError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| request, | ||
| path, | ||
| root=_METADATA_ROOT, | ||
| root=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the docstring for this says root (str):. It looks like it should be root (Optional[str]):. And the docstring should mention what happens if its left as None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| ) # path to CA certificate | ||
| client_combined_cert_path: str = field( | ||
| default_factory=_get_mds_client_combined_cert_path | ||
| ) # path to file containing client certificate and key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are marked as str, but it looks like they are paths now?
Are you running nox -s mypy checks? I'm not sure if they're configured to run in CI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I have been running mypy checks! Not sure why this isn't flagged though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, updated
sai-sunder-s
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great addition to the library. The implementation of mTLS for the GCE metadata server is well-structured and thoroughly tested.
Here's a summary of my review:
- Good separation of concerns: The mTLS logic is nicely encapsulated in the new
_mtls.pymodule. - Clear configuration: The use of the
GCE_METADATA_MTLS_MODEenvironment variable provides a clear and flexible way to configure the mTLS behavior. - Robustness: The fallback mechanism in
defaultmode is a good feature to ensure that the library can still function even if mTLS is not properly configured. - Thorough testing: The addition of new test files and the extension of existing tests provide good coverage for the new functionality.
I have one minor suggestion for improvement in google/auth/compute_engine/_metadata.py, which I've added as a line comment.
Overall, this looks ready to merge.
| if _GCE_METADATA_HOST != _GCE_DEFAULT_HOST: | ||
| # mTLS is only supported when connecting to the default metadata host. | ||
| # Raise an exception if we are in strict mode (which requires mTLS) | ||
| # but the metadata host has been overridden. (which means mTLS will fail) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation is good. For extra robustness, you could consider checking if _GCE_METADATA_HOST is in _GCE_DEFAULT_MDS_HOSTS instead of just comparing it to _GCE_DEFAULT_HOST. This would also cover the case where the IP address is used as the host. However, the current implementation is likely sufficient as it's improbable for the IP to be set as the host via the environment variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point. I will make this edit.
This is Gemini CLI's review, not mine :) |
sai-sunder-s
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. Left couple of comments.
| request.session = requests.Session() | ||
| for host in _GCE_DEFAULT_MDS_HOSTS: | ||
| request.session.mount(f"https://{host}/", adapter) | ||
| return request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are returning the same request object as the input. All changes are inside the object.
Do we really have to return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this is a good point. Changed.
| has been overridden in strict mTLS mode). | ||
| """ | ||
| use_mtls = _mtls.should_use_mds_mtls() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we determine whether to use mtls or not at cred initialization time and put it in an attribute instead of determining each time?
Do we expect the value to change after cred initilization?
daniel-sanche
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| retrieving metadata. | ||
| google.auth.exceptions.MutualTLSChannelError: if the environment | ||
| configuration is invalid for mTLS (for example, the metadata host | ||
| has been overridden in strict mTLS mode). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is only if mtls is requested and the configuration is invalid, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes that's right. updated that.
1. now we do not create a new request. instead, create an mds mtls adapter and mount it on the request session. 2. added _validate_gce_mds_configured_environment, which ensures if we are using strict, that the host being contacted is default 3. fix unit tests and add new tests
3cdf91a to
9f4bbaa
Compare
PR created by the Librarian CLI to initialize a release. Merging this PR will auto trigger a release. Librarian Version: v0.7.0 Language Image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest <details><summary>google-auth: 2.44.0</summary> ## [2.44.0](v2.43.0...v2.44.0) (2025-12-12) ### Features * MDS connections use mTLS (#1856) ([0387bb9](0387bb95)) * support Python 3.14 (#1822) ([0f7097e](0f7097e7)) * add ecdsa p-384 support (#1872) ([39c381a](39c381a5)) * Add shlex to correctly parse executable commands with spaces (#1855) ([cf6fc3c](cf6fc3cc)) * Implement token revocation in STS client and add revoke() metho… (#1849) ([d563898](d5638986)) ### Bug Fixes * Add temporary patch to workload cert logic to accomodate Cloud Run mis-configuration (#1880) ([78de790](78de7907)) * Delegate workload cert and key default lookup to helper function (#1877) ([b0993c7](b0993c7e)) * Use public refresh method for source credentials in ImpersonatedCredentials (#1884) ([e0c3296](e0c3296f)) </details>
Feature Gating
The
GCE_METADATA_MTLS_MODEenvironment variable is introduced, which can be set to strict, none, or default.The
should_use_mds_mtlsfunction determines whether to use mTLS based on the environment variable and the existence of the certificate files.Using MDS mTLS
A custom
MdsMtlsAdapterfor requests is implemented to handle the SSL context for mTLS.The
create_sessionfunction mounts an MdsMtlsAdapter into the request.SessionMdsMtlsAdapter
Loads MDS mTLS certificates from well-known location (https://docs.cloud.google.com/compute/docs/metadata/overview#https-mds-certificates).
If HTTPS request fails, MdsMtlsAdapter will fallback to HTTP.
Integrating with existing code
compute_engine/_metadata.py: