Skip to content

Commit

Permalink
Merge pull request #52 from kangwork/master
Browse files Browse the repository at this point in the history
Changed `lastActivityDescription` strings for service accounts
  • Loading branch information
kangwork authored Aug 9, 2024
2 parents 60a3f94 + 666d25e commit 065471e
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 60 deletions.
3 changes: 1 addition & 2 deletions src/plugin/connector/cloud_identity_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def list_groups(self, customer_id):
response = request.execute()
groups.extend(response.get("groups", []))
request = self.client.groups().list_next(
previous_request=request, previous_response=response
previous_request=request, previous_response=response
)
if request is None:
break
Expand Down Expand Up @@ -57,4 +57,3 @@ def list_memberships(self, parent):
def get_membership(self, name):
result = self.client.groups().memberships().get(name=name).execute()
return result

4 changes: 3 additions & 1 deletion src/plugin/connector/iam_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def list_service_accounts(self, project_id: str = None) -> list:
return service_accounts

@api_retry_handler(default_response=[])
def list_service_account_keys(self, service_account_email: str, project_id: str = None):
def list_service_account_keys(
self, service_account_email: str, project_id: str = None
):
project_id = project_id or self.project_id
query = {
"name": f"projects/{project_id}/serviceAccounts/{service_account_email}"
Expand Down
30 changes: 22 additions & 8 deletions src/plugin/connector/logging_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class LoggingConnector(GoogleCloudConnector):
version = "v2"

def __init__(self, options: dict, secret_data: dict, schema: str, *args, **kwargs):
super().__init__(options=options, secret_data=secret_data, schema=schema, *args, **kwargs)
super().__init__(
options=options, secret_data=secret_data, schema=schema, *args, **kwargs
)
self.log_search_period = options.get("log_search_period", "3 Months")

@api_retry_handler(default_response=[])
Expand All @@ -29,21 +31,30 @@ def list_entries(self, project_id: str) -> list:
entries = response.get("entries", [])
return entries

def get_last_log_entry_timestamp(self, project_id: str, service_account_email: str, service_account_key_name: str=None):
log_entries = (self._list_entries_service_accounts(project_id, service_account_email, service_account_key_name))
def get_last_log_entry_timestamp(
self,
project_id: str,
service_account_email: str,
service_account_key_name: str = None,
):
log_entries = self._list_entries_service_accounts(
project_id, service_account_email, service_account_key_name
)
if not log_entries:
return None
timestamp = log_entries[0].get("timestamp")
return timestamp

@api_retry_handler(default_response=[])
def _list_entries_service_accounts(self, project_id: str, service_account_email: str, service_account_key_name) -> list:
def _list_entries_service_accounts(
self, project_id: str, service_account_email: str, service_account_key_name
) -> list:
filter_str = (
f"protoPayload.authenticationInfo.principalEmail=\"{service_account_email}\""
f'protoPayload.authenticationInfo.principalEmail="{service_account_email}"'
)
if service_account_key_name:
full_name = f"//iam.googleapis.com/{service_account_key_name}"
filter_str += f" AND protoPayload.authenticationInfo.serviceAccountKeyName=\"{full_name}\""
resource_name = f"//iam.googleapis.com/{service_account_key_name}"
filter_str += f' AND protoPayload.authenticationInfo.serviceAccountKeyName="{resource_name}"'
filter_str += self._get_timestamp_filter_str(datetime.utcnow())

body = {
Expand All @@ -66,7 +77,10 @@ def _get_timestamp_filter_str(self, time_now: datetime) -> str:
else:
log_search_period_in_days = 365

start_time, end_time = time_now - timedelta(days=log_search_period_in_days), time_now
start_time, end_time = (
time_now - timedelta(days=log_search_period_in_days),
time_now,
)
start_time_str, end_time_str = (
start_time.isoformat().split(".")[0] + "Z",
end_time.isoformat().split(".")[0] + "Z",
Expand Down
14 changes: 11 additions & 3 deletions src/plugin/connector/resource_manager_v3_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,23 @@ def get_project_iam_policies(self, project_id: str = None):
project_id = project_id or self.project_id
resource = f"projects/{project_id}"
body = {"options": {"requestedPolicyVersion": 3}}
result = self.client.projects().getIamPolicy(resource=resource, body=body).execute()
result = (
self.client.projects().getIamPolicy(resource=resource, body=body).execute()
)
return result.get("bindings", [])

def get_folder_iam_policies(self, resource):
body = {"options": {"requestedPolicyVersion": 3}}
result = self.client.folders().getIamPolicy(resource=resource, body=body).execute()
result = (
self.client.folders().getIamPolicy(resource=resource, body=body).execute()
)
return result.get("bindings", [])

def get_organization_iam_policies(self, resource):
body = {"options": {"requestedPolicyVersion": 3}}
result = self.client.organizations().getIamPolicy(resource=resource, body=body).execute()
result = (
self.client.organizations()
.getIamPolicy(resource=resource, body=body)
.execute()
)
return result.get("bindings", [])
3 changes: 1 addition & 2 deletions src/plugin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def _create_init_metadata() -> dict:
"inventory.Region",
"inventory.ErrorResource",
],

"options_schema": {
"required": ["log_search_period"],
"type": "object",
Expand All @@ -62,7 +61,7 @@ def _create_init_metadata() -> dict:
"description": "Select the period to search for the last activity log for the service accounts.\
The longer the period, the longer it will take to collect data.",
}
}
},
},
}
}
Expand Down
1 change: 0 additions & 1 deletion src/plugin/manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from .iam import *

38 changes: 27 additions & 11 deletions src/plugin/manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,29 @@ def __init__(self, *args, **kwargs):
def __repr__(self):
return f"{self.__class__.__name__}"

def collect_resources(self, options: dict, secret_data: dict, schema: str) -> Generator[dict, None, None]:
def collect_resources(
self, options: dict, secret_data: dict, schema: str
) -> Generator[dict, None, None]:
try:
_LOGGER.debug(f"[{self.__repr__()}] Collect cloud service type: "
f"{self.cloud_service_group} > {self.cloud_service_type}")
_LOGGER.debug(
f"[{self.__repr__()}] Collect cloud service type: "
f"{self.cloud_service_group} > {self.cloud_service_type}"
)
yield self.get_cloud_service_type()

_LOGGER.debug(f"[{self.__repr__()}] Collect metrics: "
f"{self.cloud_service_group} > {self.cloud_service_type}")
_LOGGER.debug(
f"[{self.__repr__()}] Collect metrics: "
f"{self.cloud_service_group} > {self.cloud_service_type}"
)
yield from self.collect_metrics()

_LOGGER.debug(f"[{self.__repr__()}] Collect cloud services: "
f"{self.cloud_service_group} > {self.cloud_service_type}")
response_iterator = self.collect_cloud_services(options, secret_data, schema)
_LOGGER.debug(
f"[{self.__repr__()}] Collect cloud services: "
f"{self.cloud_service_group} > {self.cloud_service_type}"
)
response_iterator = self.collect_cloud_services(
options, secret_data, schema
)
for response in response_iterator:
try:
yield make_response(
Expand Down Expand Up @@ -82,7 +92,9 @@ def collect_resources(self, options: dict, secret_data: dict, schema: str) -> Ge
)

@abc.abstractmethod
def collect_cloud_services(self, options: dict, secret_data: dict, schema: str) -> Generator[dict, None, None]:
def collect_cloud_services(
self, options: dict, secret_data: dict, schema: str
) -> Generator[dict, None, None]:
raise ERROR_NOT_IMPLEMENTED()

def get_cloud_service_type(self) -> dict:
Expand Down Expand Up @@ -119,9 +131,13 @@ def collect_regions(cls, region: str = None) -> dict:

def collect_metrics(self) -> dict:
for dirname in os.listdir(os.path.join(METRIC_DIR, self.cloud_service_group)):
for filename in os.listdir(os.path.join(METRIC_DIR, self.cloud_service_group, dirname)):
for filename in os.listdir(
os.path.join(METRIC_DIR, self.cloud_service_group, dirname)
):
if filename.endswith(".yaml"):
file_path = os.path.join(METRIC_DIR, self.cloud_service_group, dirname, filename)
file_path = os.path.join(
METRIC_DIR, self.cloud_service_group, dirname, filename
)
info = utils.load_yaml_from_file(file_path)
if filename == "namespace.yaml":
yield make_response(
Expand Down
10 changes: 8 additions & 2 deletions src/plugin/manager/iam/group_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,20 @@ def get_group_members(self, group_id: str) -> list:
self.identity_connector.get_membership(member_name)
)
else:
_, cached_mem_name = self.member_id_to_membership_info[member_id].get("name").split("/memberships/")
_, cached_mem_name = (
self.member_id_to_membership_info[member_id]
.get("name")
.split("/memberships/")
)
_, curr_mem_name = member_name.split("/memberships/")
if cached_mem_name != curr_mem_name:
_LOGGER.debug(
f"[{self.__repr__()}] MEMBERSHIP_NAME: {curr_mem_name} has different member_id: {member_id} \
from (cached) {cached_mem_name}: {self.member_id_to_membership_info[member_id]}"
)
self.member_id_to_membership_info[member_id] = self.identity_connector.get_membership(member_name)
self.member_id_to_membership_info[member_id] = (
self.identity_connector.get_membership(member_name)
)
member_info = self.member_id_to_membership_info[member_id]
if member_info.get("memberType"):
member_info["memberType"] = member_info.pop("type")
Expand Down
26 changes: 19 additions & 7 deletions src/plugin/manager/iam/permission_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def __init__(self, *args, **kwargs):
"PROJECT": {},
}

def collect_cloud_services(self, options: dict, secret_data: dict, schema: str) -> Generator[dict, None, None]:
def collect_cloud_services(
self, options: dict, secret_data: dict, schema: str
) -> Generator[dict, None, None]:
self.iam_connector = IAMConnector(options, secret_data, schema)
self.rm_v3_connector = ResourceManagerV3Connector(options, secret_data, schema)

Expand Down Expand Up @@ -82,7 +84,7 @@ def collect_organization_permissions(self, organization: dict) -> None:
"targetType": "ORGANIZATION",
"id": organization_id,
"name": organization_name,
"location": organization_name
"location": organization_name,
}
bindings = self.rm_v3_connector.get_organization_iam_policies(organization_id)
for binding in bindings:
Expand Down Expand Up @@ -165,10 +167,16 @@ def parse_binding_info(self, binding: dict, target: dict) -> None:

if member_type == "serviceAccount":
if member_id in self.service_account_info:
self.permission_info[member]["memberName"] = self.service_account_info[member_id].get("name")
self.permission_info[member]["projectId"] = self.service_account_info[member_id].get("projectId")
self.permission_info[member]["memberName"] = (
self.service_account_info[member_id].get("name")
)
self.permission_info[member]["projectId"] = (
self.service_account_info[member_id].get("projectId")
)
else:
self.permission_info[member]["memberType"] = "googleManagedServiceAccount"
self.permission_info[member][
"memberType"
] = "googleManagedServiceAccount"
if target_type == "PROJECT":
self.permission_info[member]["projectId"] = target["id"]

Expand Down Expand Up @@ -199,7 +207,9 @@ def get_folder_location(self, folder_id: str) -> str:
parent = folder.get("parent")
if parent.startswith("organizations/"):
organization = self.rm_v3_connector.get_organization(parent)
location = f"{organization.get('displayName')} > {folder.get('displayName')}"
location = (
f"{organization.get('displayName')} > {folder.get('displayName')}"
)
else:
parent_location = self.get_folder_location(parent)
location = f"{parent_location} > {folder.get('displayName')}"
Expand All @@ -215,7 +225,9 @@ def get_project_location(self, project_id: str) -> str:
parent = project.get("parent")
if parent.startswith("organizations/"):
organization = self.rm_v3_connector.get_organization(parent)
location = f"{organization.get('displayName')} > {project.get('displayName')}"
location = (
f"{organization.get('displayName')} > {project.get('displayName')}"
)
else:
parent_location = self.get_folder_location(parent)
location = f"{parent_location} > {project.get('displayName')}"
Expand Down
18 changes: 13 additions & 5 deletions src/plugin/manager/iam/role_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def __init__(self, *args, **kwargs):
self.iam_connector = None
self.rm_v3_connector = None

def collect_cloud_services(self, options: dict, secret_data: dict, schema: str) -> Generator[dict, None, None]:
def collect_cloud_services(
self, options: dict, secret_data: dict, schema: str
) -> Generator[dict, None, None]:
self.iam_connector = IAMConnector(options, secret_data, schema)
self.rm_v3_connector = ResourceManagerV3Connector(options, secret_data, schema)
default_project_id = secret_data.get("project_id")
Expand All @@ -44,21 +46,27 @@ def collect_cloud_services(self, options: dict, secret_data: dict, schema: str)
for project in projects:
yield from self.collect_project_roles(project["projectId"])

def collect_organization_roles(self, organization: dict, default_project_id: str) -> Generator[dict, None, None]:
def collect_organization_roles(
self, organization: dict, default_project_id: str
) -> Generator[dict, None, None]:
organization_id = organization.get("name")
organization_name = organization.get("displayName")
location = f"organizations/{organization_name}"
roles = self.iam_connector.list_organization_roles(organization_id)
for role in roles:
yield self.make_role_info(role, default_project_id, "ORGANIZATION", location)
yield self.make_role_info(
role, default_project_id, "ORGANIZATION", location
)

def collect_project_roles(self, project_id: str) -> Generator[dict, None, None]:
roles = self.iam_connector.list_project_roles(project_id)
location = f"projects/{project_id}"
for role in roles:
yield self.make_role_info(role, project_id, "PROJECT", location)

def make_role_info(self, role: dict, project_id: str, role_type: str, location: str = None) -> dict:
def make_role_info(
self, role: dict, project_id: str, role_type: str, location: str = None
) -> dict:
name = role.get("title")
role_id = role.get("name")
role_url = role_id.replace("/", "<")
Expand Down Expand Up @@ -94,7 +102,7 @@ def make_role_info(self, role: dict, project_id: str, role_type: str, location:
reference={
"resource_id": role_id,
"external_link": f"https://console.cloud.google.com/iam-admin/roles/details/{role_url}?"
f"project={project_id}"
f"project={project_id}",
},
# data_format="grpc",
)
32 changes: 25 additions & 7 deletions src/plugin/manager/iam/service_account_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ def __init__(self, *args, **kwargs):
"PROJECT": {},
}

def collect_cloud_services(self, options: dict, secret_data: dict, schema: str) -> Generator[dict, None, None]:
def collect_cloud_services(
self, options: dict, secret_data: dict, schema: str
) -> Generator[dict, None, None]:
self.iam_connector = IAMConnector(options, secret_data, schema)
self.rm_v3_connector = ResourceManagerV3Connector(options, secret_data, schema)
self.logging_connector = LoggingConnector(options=options, secret_data=secret_data, schema=schema)
self.logging_connector = LoggingConnector(
options=options, secret_data=secret_data, schema=schema
)

# Get all projects
projects = self.rm_v3_connector.list_all_projects()
Expand Down Expand Up @@ -62,8 +66,14 @@ def make_cloud_service_info(self, service_account: dict, project_id: str) -> dic
service_account["status"] = "DISABLED"
else:
service_account["status"] = "ENABLED"
service_account["lastActivityTime"] = self.logging_connector.get_last_log_entry_timestamp(project_id, email)
service_account["lastActivityDescription"] = f"Activity log found in the last {self.logging_connector.log_search_period}" if service_account["lastActivityTime"] else f"No activity log found in the last {self.logging_connector.log_search_period}"
service_account["lastActivityTime"] = (
self.logging_connector.get_last_log_entry_timestamp(project_id, email)
)
service_account["lastActivityDescription"] = (
f"Activity log found in the past {self.logging_connector.log_search_period.lower()}"
if service_account["lastActivityTime"]
else f"No activity log found in the past {self.logging_connector.log_search_period.lower()}"
)
keys = self.get_service_account_keys(email, project_id)
service_account["keys"] = keys
service_account["keyCount"] = len(keys)
Expand All @@ -79,7 +89,7 @@ def make_cloud_service_info(self, service_account: dict, project_id: str) -> dic
reference={
"resource_id": resource_id,
"external_link": f"https://console.cloud.google.com/iam-admin/serviceaccounts/details/{unique_id}?"
f"project={project_id}"
f"project={project_id}",
},
# data_format="grpc",
)
Expand All @@ -90,8 +100,16 @@ def get_service_account_keys(self, email: str, project_id: str) -> list:
key_full_name = key.get("name")
key["name"] = key_full_name.split("/")[-1]
key["status"] = "ACTIVE"
key["lastActivityTime"] = self.logging_connector.get_last_log_entry_timestamp(project_id, email, key_full_name)
key["lastActivityDescription"] = f"Activity log found in the last {self.logging_connector.log_search_period}" if key["lastActivityTime"] else f"No activity log found in the last {self.logging_connector.log_search_period}"
key["lastActivityTime"] = (
self.logging_connector.get_last_log_entry_timestamp(
project_id, email, key_full_name
)
)
key["lastActivityDescription"] = (
f"Activity log found in the past {self.logging_connector.log_search_period.lower()}"
if key["lastActivityTime"]
else f"No activity log found in the past {self.logging_connector.log_search_period.lower()}"
)

creation_time = key.get("validAfterTime")
expiration_time = key.get("validBeforeTime")
Expand Down
1 change: 0 additions & 1 deletion src/plugin/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from plugin.utils.error_handlers import *

Loading

0 comments on commit 065471e

Please sign in to comment.