def _run_cert_provider_command(command, expect_encrypted_key=False): """Run the provided command, and return client side mTLS cert, key and passphrase. Args: command (List[str]): cert provider command. expect_encrypted_key (bool): If encrypted private key is expected. Returns: Tuple[bytes, bytes, bytes]: client certificate bytes in PEM format, key bytes in PEM format and passphrase bytes. Raises: google.auth.exceptions.ClientCertError: if problems occurs when running the cert provider command or generating cert, key and passphrase. """ try: process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() except OSError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) six.raise_from(new_exc, caught_exc) # Check cert provider command execution error. if process.returncode != 0: raise exceptions.ClientCertError( "Cert provider command returns non-zero status code %s" % process.returncode) # Extract certificate (chain), key and passphrase. cert_match = re.findall(_CERT_REGEX, stdout) if len(cert_match) != 1: raise exceptions.ClientCertError( "Client SSL certificate is missing or invalid") key_match = re.findall(_KEY_REGEX, stdout) if len(key_match) != 1: raise exceptions.ClientCertError( "Client SSL key is missing or invalid") passphrase_match = re.findall(_PASSPHRASE_REGEX, stdout) if expect_encrypted_key: if len(passphrase_match) != 1: raise exceptions.ClientCertError( "Passphrase is missing or invalid") if b"ENCRYPTED" not in key_match[0]: raise exceptions.ClientCertError( "Encrypted private key is expected") return cert_match[0], key_match[0], passphrase_match[0].strip() if b"ENCRYPTED" in key_match[0]: raise exceptions.ClientCertError( "Encrypted private key is not expected") if len(passphrase_match) > 0: raise exceptions.ClientCertError("Passphrase is not expected") return cert_match[0], key_match[0], None
def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): authed_http = google.auth.transport.urllib3.AuthorizedHttp( credentials=mock.Mock() ) mock_get_client_cert_and_key.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): authed_http.configure_mtls_channel() mock_get_client_cert_and_key.return_value = (False, None, None) with mock.patch.dict("sys.modules"): sys.modules["OpenSSL"] = None with pytest.raises(exceptions.MutualTLSChannelError): authed_http.configure_mtls_channel()
def test_get_client_ssl_credentials_failure( self, mock_check_dca_metadata_path, mock_read_dca_metadata_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): mock_check_dca_metadata_path.return_value = METADATA_PATH mock_read_dca_metadata_file.return_value = { "cert_provider_command": ["some command"] } # Mock that client cert and key are not loaded and exception is raised. mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): assert google.auth.transport.grpc.SslCredentials().ssl_credentials
def _read_dca_metadata_file(metadata_path): """Loads context aware metadata from the given path. Args: metadata_path (str): context aware metadata path. Returns: Dict[str, str]: The metadata. Raises: google.auth.exceptions.ClientCertError: If failed to parse metadata as JSON. """ try: with open(metadata_path) as f: metadata = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) six.raise_from(new_exc, caught_exc) return metadata
def get_client_ssl_credentials( generate_encrypted_key=False, context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH, ): """Returns the client side certificate, private key and passphrase. Args: generate_encrypted_key (bool): If set to True, encrypted private key and passphrase will be generated; otherwise, unencrypted private key will be generated and passphrase will be None. context_aware_metadata_path (str): The context_aware_metadata.json file path. Returns: Tuple[bool, bytes, bytes, bytes]: A boolean indicating if cert, key and passphrase are obtained, the cert bytes and key bytes both in PEM format, and passphrase bytes. Raises: google.auth.exceptions.ClientCertError: if problems occurs when getting the cert, key and passphrase. """ metadata_path = _check_dca_metadata_path(context_aware_metadata_path) if metadata_path: metadata_json = _read_dca_metadata_file(metadata_path) if _CERT_PROVIDER_COMMAND not in metadata_json: raise exceptions.ClientCertError( "Cert provider command is not found") command = metadata_json[_CERT_PROVIDER_COMMAND] if generate_encrypted_key and "--with_passphrase" not in command: command.append("--with_passphrase") # Execute the command. cert, key, passphrase = _run_cert_provider_command( command, expect_encrypted_key=generate_encrypted_key) return True, cert, key, passphrase return False, None, None, None
def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): mock_get_client_cert_and_key.side_effect = exceptions.ClientCertError() auth_session = google.auth.transport.requests.AuthorizedSession( credentials=mock.Mock() ) with pytest.raises(exceptions.MutualTLSChannelError): with mock.patch.dict( os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} ): auth_session.configure_mtls_channel() mock_get_client_cert_and_key.return_value = (False, None, None) with mock.patch.dict("sys.modules"): sys.modules["OpenSSL"] = None with pytest.raises(exceptions.MutualTLSChannelError): with mock.patch.dict( os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}, ): auth_session.configure_mtls_channel()
def test_default_client_encrypted_cert_source(has_default_client_cert_source, get_client_ssl_credentials): # Test default client cert source doesn't exist. has_default_client_cert_source.return_value = False with pytest.raises(exceptions.MutualTLSChannelError): mtls.default_client_encrypted_cert_source("cert_path", "key_path") # The following tests will assume default client cert source exists. has_default_client_cert_source.return_value = True # Test good callback. get_client_ssl_credentials.return_value = (True, b"cert", b"key", b"passphrase") callback = mtls.default_client_encrypted_cert_source( "cert_path", "key_path") with mock.patch("{}.open".format(__name__), return_value=mock.MagicMock()): assert callback() == ("cert_path", "key_path", b"passphrase") # Test bad callback which throws exception. get_client_ssl_credentials.side_effect = exceptions.ClientCertError() callback = mtls.default_client_encrypted_cert_source( "cert_path", "key_path") with pytest.raises(exceptions.MutualTLSChannelError): callback()