From d126e320769e40575b95d225e39c730edfb13286 Mon Sep 17 00:00:00 2001 From: Sergio Franco <58365614+serge-wq@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:50:31 -0600 Subject: [PATCH] Google auth dependency update (#1434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary `oauth2client` has been deprecated since 2019. Updating the library we use to authenticate with Google to the recommended alternative option, still maintained by Google. See: https://google-auth.readthedocs.io/en/master/oauth2client-deprecation.html ### Related issues or links - ### Checklist Provide proof that this works (this makes reviews move faster). Please perform one or more of the following: - [ ] Update/add unit or integration tests. - [x] Include a screenshot showing what the graph looked like before and after your changes. - [ ] Include console log trace showing what happened before and after your changes. Tested by running the gsuite module locally with some internal credentials. Module found the appropriate creds using the legacy delegated method and finished without a problem. ![Screenshot 2025-01-02 at 4 38 58 p m](https://github.com/user-attachments/assets/df669b12-4dc5-44fd-80a2-3fc4aca6c5c9) ![Screenshot 2025-01-02 at 4 39 39 p m](https://github.com/user-attachments/assets/7c3e6475-e807-4f84-a92f-bc6ff1b9177a) --------- Signed-off-by: Sergio Franco --- .gitignore | 1 + cartography/intel/gcp/__init__.py | 12 +++---- cartography/intel/gsuite/__init__.py | 52 ++++++++++++++++------------ pyproject.toml | 2 +- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index cc30a9f93c..78529d910b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ build/ generated dist/ .local +cartography/_version.py diff --git a/cartography/intel/gcp/__init__.py b/cartography/intel/gcp/__init__.py index 3f9a0f6dd4..fa75ad54fe 100644 --- a/cartography/intel/gcp/__init__.py +++ b/cartography/intel/gcp/__init__.py @@ -7,9 +7,10 @@ import googleapiclient.discovery import neo4j +from google.auth import default +from google.auth.credentials import Credentials as GoogleCredentials +from google.auth.exceptions import DefaultCredentialsError from googleapiclient.discovery import Resource -from oauth2client.client import ApplicationDefaultCredentialsError -from oauth2client.client import GoogleCredentials from cartography.config import Config from cartography.intel.gcp import compute @@ -295,10 +296,9 @@ def get_gcp_credentials() -> GoogleCredentials: """ try: # Explicitly use Application Default Credentials. - # See https://oauth2client.readthedocs.io/en/latest/source/ - # oauth2client.client.html#oauth2client.client.OAuth2Credentials - credentials = GoogleCredentials.get_application_default() - except ApplicationDefaultCredentialsError as e: + # See https://google-auth.readthedocs.io/en/master/user-guide.html#application-default-credentials + credentials, project_id = default() + except DefaultCredentialsError as e: logger.debug("Error occurred calling GoogleCredentials.get_application_default().", exc_info=True) logger.error( ( diff --git a/cartography/intel/gsuite/__init__.py b/cartography/intel/gsuite/__init__.py index 1902db15cd..2623c80a2d 100644 --- a/cartography/intel/gsuite/__init__.py +++ b/cartography/intel/gsuite/__init__.py @@ -5,11 +5,14 @@ from collections import namedtuple import googleapiclient.discovery -import httplib2 import neo4j +from google.auth.exceptions import DefaultCredentialsError +from google.auth.transport.requests import Request +from google.oauth2 import credentials +from google.oauth2 import service_account +from google.oauth2.credentials import Credentials as OAuth2Credentials +from google.oauth2.service_account import Credentials as ServiceAccountCredentials from googleapiclient.discovery import Resource -from oauth2client.client import ApplicationDefaultCredentialsError -from oauth2client.client import GoogleCredentials from cartography.config import Config from cartography.intel.gsuite import api @@ -26,21 +29,21 @@ Resources = namedtuple('Resources', 'admin') -def _get_admin_resource(credentials: GoogleCredentials) -> Resource: +def _get_admin_resource(credentials: OAuth2Credentials | ServiceAccountCredentials) -> Resource: """ Instantiates a Google API resource object to call the Google API. Used to pull users and groups. See https://developers.google.com/admin-sdk/directory/v1/guides/manage-users - :param credentials: The GoogleCredentials object + :param credentials: The credentials object :return: An admin api resource object """ return googleapiclient.discovery.build('admin', 'directory_v1', credentials=credentials, cache_discovery=False) -def _initialize_resources(credentials: GoogleCredentials) -> Resources: +def _initialize_resources(credentials: OAuth2Credentials | ServiceAccountCredentials) -> Resources: """ Create namedtuple of all resource objects necessary for Google API data gathering. - :param credentials: The GoogleCredentials object + :param credentials: The credentials object :return: namedtuple of all resource objects """ return Resources( @@ -61,14 +64,17 @@ def start_gsuite_ingestion(neo4j_session: neo4j.Session, config: Config) -> None "UPDATE_TAG": config.update_tag, } + creds: OAuth2Credentials | ServiceAccountCredentials if config.gsuite_auth_method == 'delegated': # Legacy delegated method logger.info('Attempting to authenticate to GSuite using legacy delegated method') try: - credentials = GoogleCredentials.from_stream(config.gsuite_config) - credentials = credentials.create_scoped(OAUTH_SCOPE) - credentials = credentials.create_delegated(os.environ.get('GSUITE_DELEGATED_ADMIN')) + creds = service_account.Credentials.from_service_account_file( + config.gsuite_config, + scopes=OAUTH_SCOPE, + ) + creds = creds.with_subject(os.environ.get('GSUITE_DELEGATED_ADMIN')) - except ApplicationDefaultCredentialsError as e: + except DefaultCredentialsError as e: logger.error( ( "Unable to initialize GSuite creds. If you don't have GSuite data or don't want to load " @@ -83,18 +89,18 @@ def start_gsuite_ingestion(neo4j_session: neo4j.Session, config: Config) -> None auth_tokens = json.loads(str(base64.b64decode(config.gsuite_config).decode())) logger.info('Attempting to authenticate to GSuite using OAuth') try: - credentials = GoogleCredentials( - None, - auth_tokens['client_id'], - auth_tokens['client_secret'], - auth_tokens['refresh_token'], - None, - auth_tokens['token_uri'], - 'Cartography', + creds = credentials.Credentials( + token=None, + client_id=auth_tokens['client_id'], + client_secret=auth_tokens['client_secret'], + refresh_token=auth_tokens['refresh_token'], + expiry=None, + token_uri=auth_tokens['token_uri'], + scopes=OAUTH_SCOPE, ) - credentials.refresh(httplib2.Http()) - credentials = credentials.create_scoped(OAUTH_SCOPE) - except ApplicationDefaultCredentialsError as e: + creds.refresh(Request()) + creds = creds.create_scoped(OAUTH_SCOPE) + except DefaultCredentialsError as e: logger.error( ( "Unable to initialize GSuite creds. If you don't have GSuite data or don't want to load " @@ -106,6 +112,6 @@ def start_gsuite_ingestion(neo4j_session: neo4j.Session, config: Config) -> None ) return - resources = _initialize_resources(credentials) + resources = _initialize_resources(creds) api.sync_gsuite_users(neo4j_session, resources.admin, config.update_tag, common_job_parameters) api.sync_gsuite_groups(neo4j_session, resources.admin, config.update_tag, common_job_parameters) diff --git a/pyproject.toml b/pyproject.toml index dc1d9c00b3..e862c06cf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "neo4j>=4.4.4,<5.0.0", "policyuniverse>=1.1.0.0", "google-api-python-client>=1.7.8", - "oauth2client>=4.1.3", + "google-auth>=2.37.0", "marshmallow>=3.0.0rc7", "oci>=2.71.0", "okta<1.0.0",