|
13 | 13 | # limitations under the License. |
14 | 14 | # |
15 | 15 |
|
16 | | -from typing import Optional |
17 | | -from urllib.parse import urlparse, urlunparse |
| 16 | +"""Helpers for client setup and configuration.""" |
18 | 17 |
|
19 | | -from google.auth.exceptions import MutualTLSChannelError # type: ignore |
| 18 | +import os |
| 19 | +from typing import Callable, Optional, Tuple |
20 | 20 |
|
| 21 | +from google.auth.exceptions import MutualTLSChannelError # type: ignore |
| 22 | +from google.auth.transport import mtls # type: ignore |
21 | 23 |
|
22 | | -def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: |
23 | | - """Converts api endpoint to mTLS endpoint. |
24 | 24 |
|
25 | | - Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to |
26 | | - "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. |
27 | | - Other URLs (including those that do not match these domain suffixes or |
28 | | - already contain '.mtls.') are passed through as-is. |
| 25 | +def use_client_cert_effective() -> bool: |
| 26 | + """Returns whether client certificate should be used for mTLS if the |
| 27 | + google-auth version supports should_use_client_cert automatic mTLS |
| 28 | + enablement. |
29 | 29 |
|
30 | | - Args: |
31 | | - api_endpoint (Optional[str]): the api endpoint to convert. |
| 30 | + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. |
32 | 31 |
|
33 | 32 | Returns: |
34 | | - Optional[str]: converted mTLS api endpoint. |
| 33 | + bool: whether client certificate should be used for mTLS |
| 34 | + Raises: |
| 35 | + ValueError: (If using a version of google-auth without |
| 36 | + should_use_client_cert and GOOGLE_API_USE_CLIENT_CERTIFICATE is |
| 37 | + set to an unexpected value.) |
35 | 38 | """ |
36 | | - if not api_endpoint or ".mtls." in api_endpoint.lower(): |
37 | | - return api_endpoint |
38 | | - |
39 | | - has_scheme = "://" in api_endpoint |
40 | | - if not has_scheme: |
41 | | - parsed = urlparse("//" + api_endpoint) |
42 | | - else: |
43 | | - parsed = urlparse(api_endpoint) |
44 | | - |
45 | | - host = parsed.hostname |
46 | | - if not host: |
47 | | - return api_endpoint |
48 | | - |
49 | | - port = f":{parsed.port}" if parsed.port else "" |
50 | | - |
51 | | - lowered_host = host.lower() |
52 | | - if lowered_host.endswith(".sandbox.googleapis.com"): |
53 | | - new_host = host[:-23] + ".mtls.sandbox.googleapis.com" |
54 | | - elif lowered_host.endswith(".googleapis.com"): |
55 | | - new_host = host[:-15] + ".mtls.googleapis.com" |
56 | | - else: |
57 | | - return api_endpoint |
58 | | - |
59 | | - netloc = new_host + port |
60 | | - new_parsed = parsed._replace(netloc=netloc) |
61 | | - |
62 | | - if not has_scheme: |
63 | | - return urlunparse(new_parsed)[2:] |
64 | | - else: |
65 | | - return urlunparse(new_parsed) |
| 39 | + # check if google-auth version supports should_use_client_cert for |
| 40 | + # automatic mTLS enablement |
| 41 | + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER |
| 42 | + return mtls.should_use_client_cert() |
| 43 | + else: # pragma: NO COVER |
| 44 | + # if unsupported, fallback to reading from env var |
| 45 | + use_client_cert_str = os.getenv( |
| 46 | + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" |
| 47 | + ).lower() |
| 48 | + if use_client_cert_str not in ("true", "false"): |
| 49 | + raise ValueError( |
| 50 | + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` " |
| 51 | + "must be either `true` or `false`" |
| 52 | + ) |
| 53 | + return use_client_cert_str == "true" |
66 | 54 |
|
67 | 55 |
|
68 | | -def get_api_endpoint( |
69 | | - api_override: Optional[str], |
70 | | - universe_domain: str, |
71 | | - default_universe: str, |
72 | | - default_mtls_endpoint: Optional[str], |
73 | | - default_endpoint_template: str, |
74 | | - use_mtls: bool, |
75 | | -) -> str: |
76 | | - """Return the API endpoint used by the client. |
| 56 | +def get_client_cert_source( |
| 57 | + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], |
| 58 | + use_cert_flag: bool, |
| 59 | +) -> Optional[Callable[[], Tuple[bytes, bytes]]]: |
| 60 | + """Return the client cert source to be used by the client. |
77 | 61 |
|
78 | 62 | Args: |
79 | | - api_override (Optional[str]): The API endpoint override. If specified, |
80 | | - this is always returned. |
81 | | - universe_domain (str): The universe domain used by the client. |
82 | | - default_universe (str): The default universe domain. |
83 | | - default_mtls_endpoint (Optional[str]): The default mTLS endpoint. |
84 | | - default_endpoint_template (str): The default endpoint template containing |
85 | | - a placeholder `{UNIVERSE_DOMAIN}`. |
86 | | - use_mtls (bool): Whether to use the mTLS endpoint. |
| 63 | + provided_cert_source (Callable[[], Tuple[bytes, bytes]]): The client certificate source provided. |
| 64 | + use_cert_flag (bool): A flag indicating whether to use the |
| 65 | + client certificate. |
87 | 66 |
|
88 | 67 | Returns: |
89 | | - str: The API endpoint to be used by the client. |
90 | | -
|
91 | | - Raises: |
92 | | - google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but |
93 | | - not supported in the configured universe domain. |
94 | | - ValueError: If mTLS is requested but no mTLS endpoint is available. |
| 68 | + Callable[[], Tuple[bytes, bytes]] or None: The client cert source to be used by the client. |
95 | 69 | """ |
96 | | - if api_override is not None: |
97 | | - return api_override |
98 | | - |
99 | | - if use_mtls: |
100 | | - if universe_domain.lower() != default_universe.lower(): |
101 | | - raise MutualTLSChannelError( |
102 | | - f"mTLS is not supported in any universe other than {default_universe}." |
| 70 | + if use_cert_flag: |
| 71 | + if provided_cert_source: |
| 72 | + return provided_cert_source |
| 73 | + elif ( |
| 74 | + hasattr(mtls, "has_default_client_cert_source") |
| 75 | + and mtls.has_default_client_cert_source() |
| 76 | + ): |
| 77 | + return mtls.default_client_cert_source() |
| 78 | + else: |
| 79 | + raise ValueError( |
| 80 | + "Client certificate is required for mTLS, but no client certificate source was provided or found." |
103 | 81 | ) |
104 | | - if not default_mtls_endpoint: |
105 | | - raise ValueError("mTLS endpoint is not available.") |
106 | | - return default_mtls_endpoint |
107 | | - else: |
108 | | - return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) |
109 | | - |
| 82 | + return None |
110 | 83 |
|
111 | | -def get_universe_domain( |
112 | | - universe_domain: Optional[str], |
113 | | - default_universe: str = "googleapis.com", |
114 | | -) -> str: |
115 | | - """Return the universe domain used by the client. |
116 | 84 |
|
117 | | - Args: |
118 | | - universe_domain (Optional[str]): The configured universe domain. |
119 | | - default_universe (str): The default universe domain. |
| 85 | +def read_environment_variables() -> Tuple[bool, str, Optional[str]]: |
| 86 | + """Returns the environment variables used by the client. |
120 | 87 |
|
121 | 88 | Returns: |
122 | | - str: The universe domain to be used by the client. |
| 89 | + Tuple[bool, str, Optional[str]]: returns the |
| 90 | + GOOGLE_API_USE_CLIENT_CERTIFICATE, GOOGLE_API_USE_MTLS_ENDPOINT, |
| 91 | + and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. |
123 | 92 |
|
124 | 93 | Raises: |
125 | | - ValueError: If the resolved universe domain is an empty string. |
| 94 | + ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not |
| 95 | + any of ["true", "false"]. |
| 96 | + google.auth.exceptions.MutualTLSChannelError: If |
| 97 | + GOOGLE_API_USE_MTLS_ENDPOINT is not any of |
| 98 | + ["auto", "never", "always"]. |
126 | 99 | """ |
127 | | - resolved = ( |
128 | | - universe_domain.strip() if universe_domain is not None else default_universe |
129 | | - ) |
130 | | - |
131 | | - if not resolved: |
132 | | - raise ValueError("Universe Domain cannot be an empty string.") |
133 | | - return resolved |
| 100 | + use_client_cert = use_client_cert_effective() |
| 101 | + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() |
| 102 | + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") |
| 103 | + if use_mtls_endpoint not in ("auto", "never", "always"): |
| 104 | + raise MutualTLSChannelError( |
| 105 | + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` " |
| 106 | + "must be `never`, `auto` or `always`" |
| 107 | + ) |
| 108 | + return use_client_cert, use_mtls_endpoint, universe_domain_env |
0 commit comments