From 9005f0fdc350f6d573101f418390c9ecfab4536c Mon Sep 17 00:00:00 2001 From: kaushik pal Date: Fri, 14 Mar 2025 14:03:09 +0100 Subject: [PATCH 1/2] Update iam.py added new function _get_group_members and modified the _expand_policy --- gcpdiag/queries/iam.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/gcpdiag/queries/iam.py b/gcpdiag/queries/iam.py index 93f47739f7..88166ce148 100644 --- a/gcpdiag/queries/iam.py +++ b/gcpdiag/queries/iam.py @@ -158,6 +158,37 @@ def _get_iam_role(name: str, default_project_id: str) -> Role: return parent_roles[name] +def _get_group_members(group_email: str, project_id: str, processed_groups: Optional[set] = None) -> List[str]: + """Retrieve members of the IAM group, including nested groups.""" + + if processed_groups is None: + processed_groups = set() + + # Avoid processing the same group multiple times + if group_email in processed_groups: + return [] + + processed_groups.add(group_email) + + service = googleapiclient.discovery.build('cloudidentity', 'v1', cache_discovery=False) + group_name = f'groups/{group_email}' + members = [] + + request = service.groups().memberships().list(parent=group_name) + while request is not None: + response = request.execute() + for membership in response.get('memberships', []): + member_id = membership['memberKey']['id'] + members.append(member_id) + # Check if the member is a group and resolve its members recursively + if member_id.startswith('group:'): + nested_group_members = _get_group_members(member_id.split(':')[1], project_id, processed_groups) + members.extend(nested_group_members) + request = service.groups().memberships().list_next(previous_request=request, previous_response=response) + + return members + + class BaseIAMPolicy(models.Resource): """Common class for IAM policies""" @@ -220,6 +251,13 @@ def _expand_policy(self, resource_data: Dict[str, Any]) -> Dict[str, Any]: for member in binding['members']: member_policy = policy_by_member[member] member_policy.setdefault('roles', set()).add(binding['role']) + # Check if the member is a group and expand its members + if member.startswith('group:'): + group_members = _get_group_members(member.split(':')[1], self.project_id) + for group_member in group_members: + group_member_policy = policy_by_member[group_member] + group_member_policy.setdefault('roles', set()).add(binding['role']) + # Populate cache for IAM roles used in the policy # Unlike `has_role_permissions` this part will be executed inside From 423bc357febdd65a1a3937094a399d9087f41c71 Mon Sep 17 00:00:00 2001 From: kaushik pal Date: Sun, 30 Mar 2025 00:08:12 +0100 Subject: [PATCH 2/2] using the apis module and implicit false as per review comment --- gcpdiag/queries/iam.py | 54 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/gcpdiag/queries/iam.py b/gcpdiag/queries/iam.py index 88166ce148..738c02e325 100644 --- a/gcpdiag/queries/iam.py +++ b/gcpdiag/queries/iam.py @@ -158,35 +158,39 @@ def _get_iam_role(name: str, default_project_id: str) -> Role: return parent_roles[name] -def _get_group_members(group_email: str, project_id: str, processed_groups: Optional[set] = None) -> List[str]: - """Retrieve members of the IAM group, including nested groups.""" +def _get_group_members(group_email: str, + project_id: str, + processed_groups: Optional[set] = None) -> List[str]: + """Retrieve members of the IAM group, including nested groups.""" - if processed_groups is None: - processed_groups = set() - - # Avoid processing the same group multiple times + if processed_groups: if group_email in processed_groups: - return [] + return [] - processed_groups.add(group_email) + else: + processed_groups = set() - service = googleapiclient.discovery.build('cloudidentity', 'v1', cache_discovery=False) - group_name = f'groups/{group_email}' - members = [] + processed_groups.add(group_email) - request = service.groups().memberships().list(parent=group_name) - while request is not None: - response = request.execute() - for membership in response.get('memberships', []): - member_id = membership['memberKey']['id'] - members.append(member_id) - # Check if the member is a group and resolve its members recursively - if member_id.startswith('group:'): - nested_group_members = _get_group_members(member_id.split(':')[1], project_id, processed_groups) - members.extend(nested_group_members) - request = service.groups().memberships().list_next(previous_request=request, previous_response=response) + service = apis.get_api('cloudidentity', 'v1', project_id) + group_name = f'groups/{group_email}' + members = [] - return members + request = service.groups().memberships().list(parent=group_name) + while request: + response = request.execute() + for membership in response.get('memberships', []): + member_id = membership['memberKey']['id'] + members.append(member_id) + # Check if the member is a group and resolve its members recursively + if member_id.startswith('group:'): + nested_group_members = _get_group_members( + member_id.split(':')[1], project_id, processed_groups) + members.extend(nested_group_members) + request = service.groups().memberships().list_next( + previous_request=request, previous_response=response) + + return members class BaseIAMPolicy(models.Resource): @@ -253,12 +257,12 @@ def _expand_policy(self, resource_data: Dict[str, Any]) -> Dict[str, Any]: member_policy.setdefault('roles', set()).add(binding['role']) # Check if the member is a group and expand its members if member.startswith('group:'): - group_members = _get_group_members(member.split(':')[1], self.project_id) + group_members = _get_group_members( + member.split(':')[1], self.project_id) for group_member in group_members: group_member_policy = policy_by_member[group_member] group_member_policy.setdefault('roles', set()).add(binding['role']) - # Populate cache for IAM roles used in the policy # Unlike `has_role_permissions` this part will be executed inside # `prefetch_rule` and will benefit from multi-threading execution