Skip to content

Commit 997188a

Browse files
feat: auto-enable mTLS when supported certificates are detected
Signed-off-by: Radhika Agrawal <agrawalradhika@google.com>
1 parent 623c34f commit 997188a

File tree

2 files changed

+121
-6
lines changed

2 files changed

+121
-6
lines changed

googleapiclient/discovery.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -649,16 +649,23 @@ def build_from_document(
649649

650650
# Obtain client cert and create mTLS http channel if cert exists.
651651
client_cert_to_use = None
652-
use_client_cert = os.getenv(GOOGLE_API_USE_CLIENT_CERTIFICATE, "false")
653-
if not use_client_cert in ("true", "false"):
654-
raise MutualTLSChannelError(
655-
"Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false"
652+
if hasattr(mtls, "should_use_client_cert"):
653+
use_client_cert = mtls.should_use_client_cert()
654+
else:
655+
# if unsupported, fallback to reading from env var
656+
use_client_cert_str = os.getenv(
657+
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
658+
).lower()
659+
use_client_cert = use_client_cert_str == "true"
660+
if use_client_cert_str not in ("true", "false"):
661+
raise MutualTLSChannelError(
662+
"Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false"
656663
)
657664
if client_options and client_options.client_cert_source:
658665
raise MutualTLSChannelError(
659666
"ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source."
660667
)
661-
if use_client_cert == "true":
668+
if use_client_cert:
662669
if (
663670
client_options
664671
and hasattr(client_options, "client_encrypted_cert_source")

tests/test_discovery.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,19 @@ def test_self_signed_jwt_disabled(self):
778778

779779
REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/"
780780
MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/"
781-
781+
CONFIG_DATA_WITH_WORKLOAD = {
782+
"version": 1,
783+
"cert_configs": {
784+
"workload": {
785+
"cert_path": "path/to/cert/file",
786+
"key_path": "path/to/key/file",
787+
}
788+
},
789+
}
790+
CONFIG_DATA_WITHOUT_WORKLOAD = {
791+
"version": 1,
792+
"cert_configs": {},
793+
}
782794

783795
class DiscoveryFromDocumentMutualTLS(unittest.TestCase):
784796
MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
@@ -884,6 +896,47 @@ def test_mtls_with_provided_client_cert(
884896
self.check_http_client_cert(plus, has_client_cert=use_client_cert)
885897
self.assertEqual(plus._baseUrl, base_url)
886898

899+
@parameterized.expand(
900+
[
901+
("never", "", CONFIG_DATA_WITH_WORKLOAD , REGULAR_ENDPOINT),
902+
("auto", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT),
903+
("always", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT),
904+
("never", "", CONFIG_DATA_WITHOUT_WORKLOAD, REGULAR_ENDPOINT),
905+
("auto", "", CONFIG_DATA_WITHOUT_WORKLOAD, REGULAR_ENDPOINT),
906+
("always", "", CONFIG_DATA_WITHOUT_WORKLOAD, MTLS_ENDPOINT),
907+
]
908+
)
909+
def test_mtls_with_provided_client_cert_unset_environment_variable(
910+
self, use_mtls_env, use_client_cert, config_data, base_url
911+
):
912+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
913+
self.skipTest(
914+
"The should_use_client_cert function is not available in this "
915+
"version of google-auth."
916+
)
917+
discovery = read_datafile("plus.json")
918+
config_filename = "mock_certificate_config.json"
919+
config_file_content = json.dumps(config_data)
920+
m = mock.mock_open(read_data=config_file_content)
921+
922+
with mock.patch.dict(
923+
"os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
924+
):
925+
with mock.patch.dict(
926+
"os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
927+
):
928+
with mock.patch("builtins.open", m):
929+
with mock.patch.dict("os.environ", {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}):
930+
plus = build_from_document(
931+
discovery,
932+
credentials=self.MOCK_CREDENTIALS,
933+
client_options={
934+
"client_encrypted_cert_source": self.client_encrypted_cert_source
935+
},
936+
)
937+
self.assertIsNotNone(plus)
938+
self.assertEqual(plus._baseUrl, base_url)
939+
887940
@parameterized.expand(
888941
[
889942
("never", "true"),
@@ -961,6 +1014,61 @@ def test_mtls_with_default_client_cert(
9611014
self.assertIsNotNone(plus)
9621015
self.check_http_client_cert(plus, has_client_cert=use_client_cert)
9631016
self.assertEqual(plus._baseUrl, base_url)
1017+
@parameterized.expand(
1018+
[
1019+
("never", "", CONFIG_DATA_WITH_WORKLOAD, REGULAR_ENDPOINT),
1020+
("auto", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT),
1021+
("always", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT),
1022+
("never", "", CONFIG_DATA_WITHOUT_WORKLOAD, REGULAR_ENDPOINT),
1023+
("auto", "", CONFIG_DATA_WITHOUT_WORKLOAD, REGULAR_ENDPOINT),
1024+
("always", "", CONFIG_DATA_WITHOUT_WORKLOAD, MTLS_ENDPOINT),
1025+
]
1026+
)
1027+
@mock.patch(
1028+
"google.auth.transport.mtls.has_default_client_cert_source", autospec=True
1029+
)
1030+
@mock.patch(
1031+
"google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True
1032+
)
1033+
def test_mtls_with_default_client_cert_with_unset_environment_variable(
1034+
self,
1035+
use_mtls_env,
1036+
use_client_cert,
1037+
config_data,
1038+
base_url,
1039+
default_client_encrypted_cert_source,
1040+
has_default_client_cert_source,
1041+
):
1042+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
1043+
self.skipTest(
1044+
"The should_use_client_cert function is not available in this "
1045+
"version of google-auth."
1046+
)
1047+
has_default_client_cert_source.return_value = True
1048+
default_client_encrypted_cert_source.return_value = (
1049+
self.client_encrypted_cert_source
1050+
)
1051+
discovery = read_datafile("plus.json")
1052+
config_filename = "mock_certificate_config.json"
1053+
config_file_content = json.dumps(config_data)
1054+
m = mock.mock_open(read_data=config_file_content)
1055+
1056+
with mock.patch.dict(
1057+
"os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env}
1058+
):
1059+
with mock.patch.dict(
1060+
"os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert}
1061+
):
1062+
with mock.patch("builtins.open", m):
1063+
with mock.patch.dict("os.environ", {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}):
1064+
plus = build_from_document(
1065+
discovery,
1066+
credentials=self.MOCK_CREDENTIALS,
1067+
adc_cert_path=self.ADC_CERT_PATH,
1068+
adc_key_path=self.ADC_KEY_PATH,
1069+
)
1070+
self.assertIsNotNone(plus)
1071+
self.assertEqual(plus._baseUrl, base_url)
9641072

9651073
@parameterized.expand(
9661074
[

0 commit comments

Comments
 (0)