Skip to content

Commit

Permalink
Pull request update/241206
Browse files Browse the repository at this point in the history
f88aa20 OS-8047. Fixed invalid reason for gcp inactive_users archive
daaa7da OS-8043. Add the GCP service to the Inactive IAM usersrecommendation
 936dc52 OS-8003. inactive_users for gcp
  • Loading branch information
stanfra authored Dec 6, 2024
2 parents 7bb6652 + f88aa20 commit aab38e0
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 15 deletions.
4 changes: 3 additions & 1 deletion bumiworker/bumiworker/modules/archive/inactive_users.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging

from bumiworker.bumiworker.consts import ArchiveReason
from bumiworker.bumiworker.modules.inactive_users_base import ArchiveInactiveUsersBase
from bumiworker.bumiworker.modules.inactive_users_base import (
ArchiveInactiveUsersBase)
from bumiworker.bumiworker.modules.recommendations.inactive_users import (
InactiveUsers as InactiveUsersRecommendation)

Expand All @@ -12,6 +13,7 @@
class InactiveUsers(ArchiveInactiveUsersBase, InactiveUsersRecommendation):
SUPPORTED_CLOUD_TYPES = [
'aws_cnr',
'gcp_cnr',
'nebius'
]

Expand Down
45 changes: 39 additions & 6 deletions bumiworker/bumiworker/modules/recommendations/inactive_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@


DEFAULT_DAYS_THRESHOLD = 90
INTERVAL = 300
GCP_METRIC_NAME = 'iam.googleapis.com/service_account/authn_events_count'
MSEC_IN_SEC = 1000
LOG = logging.getLogger(__name__)


class InactiveUsers(InactiveUsersBase):
SUPPORTED_CLOUD_TYPES = [
'aws_cnr',
'gcp_cnr',
'nebius'
]

Expand All @@ -28,6 +31,8 @@ def list_users(self, cloud_adapter):
result = []
for folder_id in cloud_adapter.folders:
result.extend(cloud_adapter.service_accounts_list(folder_id))
elif cloud_type == 'gcp_cnr':
result = cloud_adapter.service_accounts_list()
else:
result = cloud_adapter.list_users()
return result
Expand Down Expand Up @@ -56,6 +61,33 @@ def is_outdated(last_used_):
'last_used': int(last_used.timestamp())
}

def handle_gcp_user(self, user, now, cloud_adapter, days_threshold):
last_used = 0
service_account_id = user.unique_id
inactive_threshold = self._get_inactive_threshold(days_threshold)
end_date = now
# there is no created_at for service account, so extend dates range to
# try to get last_used
start_date = now - inactive_threshold - inactive_threshold
service_account_usage = cloud_adapter.get_metric(
GCP_METRIC_NAME, [service_account_id], INTERVAL, start_date,
end_date, id_field='unique_id'
)
used_dates = [
point.interval.end_time for data in service_account_usage
for point in data.points if point.value.double_value != 0
]
if used_dates:
last_used_dt = max(used_dates)
last_used = int(last_used_dt.timestamp())
if not self._is_outdated(now, last_used_dt, inactive_threshold):
return
return {
'user_name': user.display_name,
'user_id': service_account_id,
'last_used': last_used
}

def handle_nebius_user(self, user, now, cloud_adapter, days_threshold):
service_account_id = user['id']
folder_id = user['folderId']
Expand Down Expand Up @@ -99,12 +131,13 @@ def handle_nebius_user(self, user, now, cloud_adapter, days_threshold):

def handle_user(self, user, now, cloud_adapter, days_threshold):
cloud_type = cloud_adapter.config['type']
if cloud_type == 'aws_cnr':
return self.handle_aws_user(user, now, cloud_adapter,
days_threshold)
else:
return self.handle_nebius_user(user, now, cloud_adapter,
days_threshold)
cloud_func_map = {
"aws_cnr": self.handle_aws_user,
"gcp_cnr": self.handle_gcp_user,
"nebius": self.handle_nebius_user,
}
func = cloud_func_map[cloud_type]
return func(user, now, cloud_adapter, days_threshold)


def main(organization_id, config_client, created_at, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import InactiveUsersModal from "components/SideModalManager/SideModals/recommendations/InactiveUsersModal";
import { AWS_IAM, NEBIUS_SERVICE } from "hooks/useRecommendationServices";
import { AWS_IAM, GCP_IAM, NEBIUS_SERVICE } from "hooks/useRecommendationServices";
import { detectedAt, lastUsed, name, userLocation } from "utils/columns";
import { AWS_CNR, NEBIUS } from "utils/constants";
import { AWS_CNR, GCP_CNR, NEBIUS } from "utils/constants";
import BaseRecommendation, { CATEGORY_SECURITY } from "./BaseRecommendation";

const columns = [
Expand All @@ -28,9 +28,9 @@ class InactiveUsers extends BaseRecommendation {

emptyMessageId = "noInactiveUsers";

services = [AWS_IAM, NEBIUS_SERVICE];
services = [AWS_IAM, NEBIUS_SERVICE, GCP_IAM];

appliedDataSources = [AWS_CNR, NEBIUS];
appliedDataSources = [AWS_CNR, NEBIUS, GCP_CNR];

categories = [CATEGORY_SECURITY];

Expand Down
5 changes: 5 additions & 0 deletions ngui/ui/src/hooks/useRecommendationServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const AZURE_COMPUTE = "azureCompute";
export const AZURE_NETWORK = "azureNetwork";

export const GCP_COMPUTE_ENGINE = "gcpComputeEngine";
export const GCP_IAM = "gcpAim";

export const NEBIUS_SERVICE = "nebius";

Expand Down Expand Up @@ -88,6 +89,10 @@ const GCP_SERVICES = Object.freeze({
[GCP_COMPUTE_ENGINE]: {
type: GCP_CNR,
name: "services.computeEngine"
},
[GCP_IAM]: {
type: GCP_CNR,
name: "services.iam"
}
});

Expand Down
22 changes: 18 additions & 4 deletions tools/cloud_adapter/clouds/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from google.cloud import compute
from google.cloud import storage
from google.cloud import monitoring_v3
from google.cloud.iam_admin_v1 import IAMClient, types

import tools.cloud_adapter.exceptions
import tools.cloud_adapter.model
Expand Down Expand Up @@ -731,6 +732,12 @@ def compute_networks_client(self):
self.credentials,
)

@cached_property
def iam_client(self):
return IAMClient.from_service_account_info(
self.credentials,
)

@cached_property
def storage_client(self):
return storage.Client.from_service_account_info(
Expand Down Expand Up @@ -1337,21 +1344,23 @@ def _metrics_aggregation(interval):
)

@staticmethod
def _metrics_filter(metric_name, instance_ids):
def _metrics_filter(metric_name, instance_ids, id_field):
type_filter = f'metric.type = "{metric_name}"'
instance_ids_filter = " OR ".join(
[
"resource.labels.instance_id = " + instance_id
f"resource.labels.{id_field} = " + instance_id
for instance_id in instance_ids
]
)
return f"{type_filter} AND ({instance_ids_filter})"

def get_metric(self, metric_name, instance_ids, interval, start_date, end_date):
def get_metric(self, metric_name, instance_ids, interval, start_date,
end_date, id_field="instance_id"):
results = self.metrics_client.list_time_series(
request={
"name": f"projects/{self.project_id}",
"filter": self._metrics_filter(metric_name, instance_ids),
"filter": self._metrics_filter(metric_name, instance_ids,
id_field),
"interval": self._metrics_interval(start_date, end_date),
"view": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,
"aggregation": self._metrics_aggregation(interval),
Expand Down Expand Up @@ -1602,6 +1611,11 @@ def _get_region_locations(self, region: str) -> list[str]:
raise RegionNotFoundException(f"Region `{region}` was not found in cloud")
return locations

def service_accounts_list(self):
request = types.ListServiceAccountsRequest()
request.name = f"projects/{self.project_id}"
return list(self.iam_client.list_service_accounts(request=request))

def start_instance(self, instance_name, zone):
try:
self.compute_instances_client.start(
Expand Down
1 change: 1 addition & 0 deletions tools/cloud_adapter/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"azure-identity==1.6.1",

# Gcp
'google-cloud-iam==2.16.1',
'google-cloud-bigquery==3.11.4',
'google-cloud-compute==1.14.1',
'google-cloud-storage==2.10.0',
Expand Down

0 comments on commit aab38e0

Please sign in to comment.