Skip to content
Open
19 changes: 12 additions & 7 deletions packages/google-auth/google/auth/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
from urllib.parse import urlparse
import warnings


from google.auth import _helpers, environment_vars
from google.auth import _regional_access_boundary_utils
from google.auth import exceptions
from google.auth import metrics
from google.auth import (
_helpers,
_regional_access_boundary_utils,
environment_vars,
exceptions,
metrics,
)
from google.auth._credentials_base import _BaseCredentials
from google.auth._refresh_worker import RefreshThreadManager

Expand Down Expand Up @@ -532,7 +534,9 @@ def _lookup_regional_access_boundary(
Returns:
Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed.
"""
from google.oauth2 import _client
import importlib

_client = importlib.import_module("google.oauth2._client")

url = self._build_regional_access_boundary_lookup_url(request=request)
if not url:
Expand All @@ -547,7 +551,8 @@ def _lookup_regional_access_boundary(

@abc.abstractmethod
def _build_regional_access_boundary_lookup_url(
self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821
self,
request: "Optional[google.auth.transport.Request]" = None, # noqa: F821
):
"""
Builds and returns the URL for the Regional Access Boundary lookup API.
Expand Down
17 changes: 10 additions & 7 deletions packages/google-auth/google/auth/crypt/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@
for implmentations using different third party libraries
"""

from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey

from google.auth import _helpers
from google.auth.crypt import _cryptography_rsa
from google.auth.crypt import base
from google.auth.crypt import _cryptography_rsa, base

RSA_KEY_MODULE_PREFIX = "rsa.key"

Expand Down Expand Up @@ -92,10 +90,9 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin):
"""

def __init__(self, private_key, key_id=None):
module_str = private_key.__class__.__module__
if isinstance(private_key, RSAPrivateKey):
if private_key is None or isinstance(private_key, RSAPrivateKey):
impl_lib = _cryptography_rsa
elif module_str.startswith(RSA_KEY_MODULE_PREFIX):
elif private_key.__class__.__module__.startswith(RSA_KEY_MODULE_PREFIX):
from google.auth.crypt import _python_rsa

impl_lib = _python_rsa
Expand All @@ -106,6 +103,12 @@ def __init__(self, private_key, key_id=None):
@property # type: ignore
@_helpers.copy_docstring(base.Signer)
def key_id(self):
# Support subclasses or mock signer implementations (e.g. in test suites)
# that define `_key_id` directly on the outer instance instead of fully
# initializing an underlying `_impl` backend.
key_id = getattr(self, "_key_id", None)
if key_id is not None:
return key_id

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.

Is this to support subclasses? (If so, maybe add a comment?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a comment

return self._impl.key_id

@_helpers.copy_docstring(base.Signer)
Expand Down
31 changes: 19 additions & 12 deletions packages/google-auth/google/auth/impersonated_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@
import logging
from typing import Optional, TYPE_CHECKING


from google.auth import _exponential_backoff
from google.auth import _helpers
from google.auth import _regional_access_boundary_utils
from google.auth import credentials
from google.auth import exceptions
from google.auth import iam
from google.auth import jwt
from google.auth import metrics
from google.auth import (
_exponential_backoff,
_helpers,
_regional_access_boundary_utils,
credentials,
exceptions,
iam,
jwt,
metrics,
)
from google.oauth2 import _client

if TYPE_CHECKING: # pragma: NO COVER
Expand Down Expand Up @@ -262,7 +263,12 @@ def __init__(
):
self._source_credentials._create_self_signed_jwt(None)

self._universe_domain = source_credentials.universe_domain
universe_domain = getattr(source_credentials, "universe_domain", None)
self._universe_domain = (
universe_domain
if isinstance(universe_domain, str)
else credentials.DEFAULT_UNIVERSE_DOMAIN
)
Comment thread
macastelaz marked this conversation as resolved.

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.

This seems like it could be a pretty big behaviour change. Are you sure this is safe?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It should be safe (I know, famous last words): the base credential class already sets this same default (

self._universe_domain = DEFAULT_UNIVERSE_DOMAIN
) and if the universe domain is set to something else, it would still be respected here. There are cases where you have an older custom credential class or what we encountered in mock credentials used in tests where the universe domain isn't set and the class doesn't extend the base, but I think even in these cases, using DEFAULT_UNIVERSE_DOMAIN is expected across Google client libraries.

Thoughts?

self._target_principal = target_principal
self._target_scopes = target_scopes
self._delegates = delegates
Expand Down Expand Up @@ -352,7 +358,8 @@ def _perform_refresh_token(self, request):
)

def _build_regional_access_boundary_lookup_url(
self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821
self,
request: "Optional[google.auth.transport.Request]" = None, # noqa: F821
):
"""Builds and returns the URL for the Regional Access Boundary lookup API.

Expand Down Expand Up @@ -564,7 +571,7 @@ def __init__(

if not isinstance(target_credentials, Credentials):
raise exceptions.GoogleAuthError(
"Provided Credential must be " "impersonated_credentials"
"Provided Credential must be impersonated_credentials"
)
self._target_credentials = target_credentials
self._target_audience = target_audience
Expand Down
11 changes: 7 additions & 4 deletions packages/google-auth/google/auth/transport/_mtls_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
import re
import subprocess

from google.auth import _agent_identity_utils
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import _agent_identity_utils, environment_vars, exceptions

CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json"

Expand Down Expand Up @@ -453,7 +451,7 @@ def check_use_client_cert():

If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding
bool value will be returned. If the value is set to an unexpected string, it
will default to False.
will raise a ValueError.
If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred
by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying
it contains a "workload" section. If so, the function will return True,
Expand All @@ -470,6 +468,11 @@ def check_use_client_cert():

# Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set.
if use_client_cert:
if use_client_cert.lower() not in ("true", "false"):
raise ValueError(
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
" either `true` or `false`"
)

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.

I don't think we should be reverting this kind of behaviour. It was an explicit decision to return False instead of raise an exception here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do you by chance have any additional context you could share on the original decision to return False here? From my view point, it feels like a security risk to not fail fast here. E.g. if a user sets this to ture trying to set the desire to use a client cert for an mtls connection and we silently "downgrade" this to false because of the typo, I'm not sure it's a great result. By raising here, we fail fast and close that "hole".

FWIW, there are internal tests for gapic generated clients that are asserting an error is raised so if we do stick with returning false, we'll likely need to update a lot of older APIs, etc.

FWIW, we currently have a discrepancy in

which raises the error as well as in the gapic generator ( )

return use_client_cert.lower() == "true"
else:
# Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set.
Expand Down
17 changes: 15 additions & 2 deletions packages/google-auth/tests/transport/test__mtls_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,13 @@ def test_override_does_not_exist(self):
returned_path = _mtls_helper._get_cert_config_path(config_path)
assert returned_path is None

@mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""})
@mock.patch.dict(
os.environ,
{
"GOOGLE_API_CERTIFICATE_CONFIG": "",
"CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "",
},
)
@mock.patch("os.path.exists", autospec=True)
def test_default(self, mock_path_exists):
mock_path_exists.return_value = True
Expand Down Expand Up @@ -788,13 +794,17 @@ def test_env_var_explicit_false(self):

@mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "garbage"})
def test_env_var_explicit_garbage(self):
assert _mtls_helper.check_use_client_cert() is False
import pytest

with pytest.raises(ValueError, match="must be either `true` or `false`"):
_mtls_helper.check_use_client_cert()

@mock.patch("builtins.open", autospec=True)
@mock.patch.dict(
os.environ,
{
"GOOGLE_API_USE_CLIENT_CERTIFICATE": "",
"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "",
"GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config",
},
)
Expand All @@ -810,6 +820,7 @@ def test_config_file_success(self, mock_file):
os.environ,
{
"GOOGLE_API_USE_CLIENT_CERTIFICATE": "",
"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "",
"GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config",
},
)
Expand All @@ -822,6 +833,7 @@ def test_config_file_missing_keys(self, mock_file):
os.environ,
{
"GOOGLE_API_USE_CLIENT_CERTIFICATE": "",
"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "",
"GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config",
},
)
Expand All @@ -834,6 +846,7 @@ def test_config_file_bad_json(self, mock_file):
os.environ,
{
"GOOGLE_API_USE_CLIENT_CERTIFICATE": "",
"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "",
"GOOGLE_API_CERTIFICATE_CONFIG": "/path/does/not/exist",
},
)
Expand Down
Loading