mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(gcp): add check for unused Service Accounts (#7419)
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
@@ -72,7 +72,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|
||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) |
|
||||
|---|---|---|---|---|
|
||||
| AWS | 564 | 82 | 33 | 10 |
|
||||
| GCP | 78 | 13 | 7 | 3 |
|
||||
| GCP | 79 | 13 | 7 | 3 |
|
||||
| Azure | 140 | 18 | 8 | 3 |
|
||||
| Kubernetes | 83 | 7 | 4 | 7 |
|
||||
| Microsoft365 | 5 | 2 | 1 | 0 |
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
"iam_sa_no_administrative_privileges",
|
||||
"iam_sa_no_user_managed_keys",
|
||||
"iam_sa_user_managed_key_rotate_90_days",
|
||||
"iam_service_account_unused",
|
||||
"apikeys_key_rotated_in_90_days",
|
||||
"apikeys_api_restrictions_configured"
|
||||
],
|
||||
|
||||
@@ -447,6 +447,10 @@ gcp:
|
||||
# GCP Compute Configuration
|
||||
# gcp.compute_public_address_shodan
|
||||
shodan_api_key: null
|
||||
# GCP Service Account and user-managed keys unused configuration
|
||||
# gcp.iam_service_account_unused
|
||||
# gcp.iam_sa_user_managed_key_unused
|
||||
max_unused_account_days: 180
|
||||
|
||||
# Kubernetes Configuration
|
||||
kubernetes:
|
||||
|
||||
+2
-2
@@ -8,14 +8,14 @@
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "ServiceAccountKey",
|
||||
"Description": "Ensure That There Are No Dormant Service Account Keys for Each Service Account. A key is considered dormant if it has been inactive for more than 180 days.",
|
||||
"Description": "Ensure That There Are No Unused Service Account Keys for Each Service Account.",
|
||||
"Risk": "Anyone who has access to the keys will be able to access resources through the service account. GCP-managed keys are used by Cloud Platform services such as App Engine and Compute Engine. These keys cannot be downloaded. Google will keep the keys and automatically rotate them on an approximately weekly basis. User-managed keys are created, downloadable, and managed by users.",
|
||||
"RelatedUrl": "https://cloud.google.com/iam/docs/service-account-overview#identify-unused",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-user-managed-service-account-keys.html",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
|
||||
+5
-2
@@ -8,6 +8,9 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class iam_sa_user_managed_key_unused(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
max_unused_days = monitoring_client.audit_config.get(
|
||||
"max_unused_account_days", 180
|
||||
)
|
||||
keys_used = monitoring_client.sa_keys_metrics
|
||||
for account in iam_client.service_accounts:
|
||||
for key in account.keys:
|
||||
@@ -21,10 +24,10 @@ class iam_sa_user_managed_key_unused(Check):
|
||||
)
|
||||
if key.name in keys_used:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"User-managed key {key.name} for Service Account {account.email} was used over the last 180 days."
|
||||
report.status_extended = f"User-managed key {key.name} for Service Account {account.email} was used over the last {max_unused_days} days."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"User-managed key {key.name} for Service Account {account.email} was not used over the last 180 days."
|
||||
report.status_extended = f"User-managed key {key.name} for Service Account {account.email} was not used over the last {max_unused_days} days."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@@ -35,6 +35,7 @@ class IAM(GCPService):
|
||||
email=account["email"],
|
||||
display_name=account.get("displayName", ""),
|
||||
project_id=project_id,
|
||||
uniqueId=account.get("uniqueId", ""),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -99,6 +100,7 @@ class ServiceAccount(BaseModel):
|
||||
display_name: str
|
||||
keys: list[Key] = []
|
||||
project_id: str
|
||||
uniqueId: str
|
||||
|
||||
|
||||
class AccessApproval(GCPService):
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "iam_service_account_unused",
|
||||
"CheckTitle": "Ensure That There Are No Unused Service Accounts",
|
||||
"CheckType": [],
|
||||
"ServiceName": "iam",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "ServiceAccount",
|
||||
"Description": "Ensure That There Are No Unused Service Accounts.",
|
||||
"Risk": "A malicious actor could make use of privilege escalation or impersonation to access an unused Service Account that is over-privileged.",
|
||||
"RelatedUrl": "https://cloud.google.com/iam/docs/service-account-overview#identify-unused",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "It is recommended to disable or remove unused Service Accounts.",
|
||||
"Url": "https://cloud.google.com/iam/docs/service-account-overview#identify-unused"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.iam.iam_client import iam_client
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
|
||||
|
||||
class iam_service_account_unused(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
max_unused_days = monitoring_client.audit_config.get(
|
||||
"max_unused_account_days", 180
|
||||
)
|
||||
sa_ids_used = monitoring_client.sa_api_metrics
|
||||
for account in iam_client.service_accounts:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=account,
|
||||
resource_id=account.email,
|
||||
location=iam_client.region,
|
||||
)
|
||||
if account.uniqueId in sa_ids_used:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Service Account {account.email} was used over the last {max_unused_days} days."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Service Account {account.email} was not used over the last {max_unused_days} days."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -12,10 +12,12 @@ class Monitoring(GCPService):
|
||||
super().__init__(__class__.__name__, provider, api_version="v3")
|
||||
self.alert_policies = []
|
||||
self.sa_keys_metrics = set()
|
||||
self.sa_api_metrics = set()
|
||||
self._get_alert_policies()
|
||||
self._get_sa_keys_metrics(
|
||||
"iam.googleapis.com/service_account/key/authn_events_count"
|
||||
)
|
||||
self._get_sa_api_metrics("serviceruntime.googleapis.com/api/request_count")
|
||||
|
||||
def _get_alert_policies(self):
|
||||
for project_id in self.project_ids:
|
||||
@@ -54,6 +56,7 @@ class Monitoring(GCPService):
|
||||
|
||||
def _get_sa_keys_metrics(self, metric_type):
|
||||
try:
|
||||
max_unused_days = int(self.audit_config.get("max_unused_account_days", 180))
|
||||
end_time = (
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
.replace(microsecond=0)
|
||||
@@ -62,7 +65,7 @@ class Monitoring(GCPService):
|
||||
start_time = (
|
||||
(
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
- datetime.timedelta(days=180)
|
||||
- datetime.timedelta(days=max_unused_days)
|
||||
)
|
||||
.replace(microsecond=0)
|
||||
.isoformat()
|
||||
@@ -96,6 +99,53 @@ class Monitoring(GCPService):
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_sa_api_metrics(self, metric_type):
|
||||
try:
|
||||
max_unused_days = int(self.audit_config.get("max_unused_account_days", 180))
|
||||
end_time = (
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
.replace(microsecond=0)
|
||||
.isoformat()
|
||||
)
|
||||
start_time = (
|
||||
(
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
- datetime.timedelta(days=max_unused_days)
|
||||
)
|
||||
.replace(microsecond=0)
|
||||
.isoformat()
|
||||
)
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
request = (
|
||||
self.client.projects()
|
||||
.timeSeries()
|
||||
.list(
|
||||
name=f"projects/{project_id}",
|
||||
filter=f'metric.type = "{metric_type}"',
|
||||
interval_startTime=start_time,
|
||||
interval_endTime=end_time,
|
||||
view="HEADERS",
|
||||
)
|
||||
)
|
||||
response = request.execute()
|
||||
|
||||
for metric in response.get("timeSeries", []):
|
||||
sa_id = metric["resource"]["labels"].get("credential_id")
|
||||
if sa_id and "serviceaccount:" in sa_id:
|
||||
self.sa_api_metrics.add(
|
||||
sa_id.replace("serviceaccount:", "")
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class AlertPolicy(BaseModel):
|
||||
name: str
|
||||
|
||||
@@ -324,7 +324,7 @@ config_azure = {
|
||||
"recommended_minimal_tls_versions": ["1.2", "1.3"],
|
||||
}
|
||||
|
||||
config_gcp = {"shodan_api_key": None}
|
||||
config_gcp = {"shodan_api_key": None, "max_unused_account_days": 30}
|
||||
|
||||
config_kubernetes = {
|
||||
"audit_log_maxbackup": 10,
|
||||
|
||||
@@ -399,6 +399,7 @@ gcp:
|
||||
# GCP Compute Configuration
|
||||
# gcp.compute_public_address_shodan
|
||||
shodan_api_key: null
|
||||
max_unused_account_days: 30
|
||||
|
||||
# Kubernetes Configuration
|
||||
kubernetes:
|
||||
|
||||
@@ -358,35 +358,73 @@ def mock_api_projects_calls(client: MagicMock):
|
||||
"metric": {
|
||||
"labels": {
|
||||
"key_id": "key1",
|
||||
"type": "iam.googleapis.com/service_account/key/authn_events_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "iam_service_account",
|
||||
"labels": {
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"unique_id": "111222233334444",
|
||||
},
|
||||
"type": "iam.googleapis.com/service_account/key/authn_events_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "iam_service_account",
|
||||
"labels": {
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"unique_id": "111222233334444",
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
}
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"labels": {
|
||||
"key_id": "key2",
|
||||
"type": "iam.googleapis.com/service_account/key/authn_events_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "iam_service_account",
|
||||
"labels": {
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"unique_id": "111222233334444",
|
||||
},
|
||||
"type": "iam.googleapis.com/service_account/key/authn_events_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "iam_service_account",
|
||||
"labels": {
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"unique_id": "111222233334444",
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
}
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"labels": {
|
||||
"response_code": "200",
|
||||
"protocol": "https",
|
||||
},
|
||||
"type": "serviceruntime.googleapis.com/api/request_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "consumed_api",
|
||||
"labels": {
|
||||
"service": "securitycenter.googleapis.com",
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"credential_id": "serviceaccount:111222233334444",
|
||||
},
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"labels": {
|
||||
"response_code": "200",
|
||||
"protocol": "https",
|
||||
},
|
||||
"type": "serviceruntime.googleapis.com/api/request_count",
|
||||
},
|
||||
"resource": {
|
||||
"type": "consumed_api",
|
||||
"labels": {
|
||||
"service": "securitycenter.googleapis.com",
|
||||
"project_id": "{GCP_PROJECT_ID}",
|
||||
"credential_id": "serviceaccount:55566666777888999",
|
||||
},
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "INT64",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -398,11 +436,13 @@ def mock_api_projects_calls(client: MagicMock):
|
||||
"name": f"projects/{GCP_PROJECT_ID}/serviceAccounts/service-account1@{GCP_PROJECT_ID}.iam.gserviceaccount.com",
|
||||
"email": "service-account1@gmail.com",
|
||||
"displayName": "Service Account 1",
|
||||
"uniqueId": "111222233334444",
|
||||
},
|
||||
{
|
||||
"name": f"projects/{GCP_PROJECT_ID}/serviceAccounts/service-account2@{GCP_PROJECT_ID}.iam.gserviceaccount.com",
|
||||
"email": "service-account2@gmail.com",
|
||||
"displayName": "Service Account 2",
|
||||
"uniqueId": "55566666777888999",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -86,7 +86,10 @@ class TestGCPProvider:
|
||||
assert gcp_provider.projects == projects
|
||||
assert gcp_provider.default_project_id == "test-project"
|
||||
assert gcp_provider.identity == GCPIdentityInfo(profile="default")
|
||||
assert gcp_provider.audit_config == {"shodan_api_key": None}
|
||||
assert gcp_provider.audit_config == {
|
||||
"shodan_api_key": None,
|
||||
"max_unused_account_days": 180,
|
||||
}
|
||||
|
||||
@freeze_time(datetime.today())
|
||||
def test_is_project_matching(self):
|
||||
|
||||
+12
-6
@@ -34,7 +34,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_iam_sa_dormant_no_keys(self):
|
||||
def test_iam_sa_unused_no_keys(self):
|
||||
iam_client = mock.MagicMock()
|
||||
monitoring_client = mock.MagicMock()
|
||||
|
||||
@@ -67,6 +67,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -78,7 +79,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_iam_sa_dormant_system_managed_keys(self):
|
||||
def test_iam_sa_unused_system_managed_keys(self):
|
||||
iam_client = mock.MagicMock()
|
||||
monitoring_client = mock.MagicMock()
|
||||
|
||||
@@ -122,6 +123,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -177,12 +179,14 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
monitoring_client.sa_keys_metrics = set(
|
||||
["90c48f61c65cd56224a12ab18e6ee9ca9c3aee7c"]
|
||||
)
|
||||
monitoring_client.audit_config = {"max_unused_account_days": 30}
|
||||
|
||||
check = iam_sa_user_managed_key_unused()
|
||||
result = check.execute()
|
||||
@@ -190,14 +194,14 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[0].name} for Service Account {iam_client.service_accounts[0].email} was used over the last 180 days."
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[0].name} for Service Account {iam_client.service_accounts[0].email} was used over the last 30 days."
|
||||
)
|
||||
assert result[0].resource_id == iam_client.service_accounts[0].keys[0].name
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||
assert result[0].resource_name == iam_client.service_accounts[0].email
|
||||
|
||||
def test_iam_sa_dormant_mixed_keys(self):
|
||||
def test_iam_sa_unused_mixed_keys(self):
|
||||
iam_client = mock.MagicMock()
|
||||
monitoring_client = mock.MagicMock()
|
||||
|
||||
@@ -255,12 +259,14 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
),
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
monitoring_client.sa_keys_metrics = set(
|
||||
["f8e4771561be5cda9b1267add7006c5143e3a220"]
|
||||
)
|
||||
monitoring_client.audit_config = {"max_unused_account_days": 30}
|
||||
|
||||
check = iam_sa_user_managed_key_unused()
|
||||
result = check.execute()
|
||||
@@ -268,7 +274,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[1].name} for Service Account {iam_client.service_accounts[0].email} was not used over the last 180 days."
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[1].name} for Service Account {iam_client.service_accounts[0].email} was not used over the last 30 days."
|
||||
)
|
||||
assert result[0].resource_id == iam_client.service_accounts[0].keys[1].name
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
@@ -278,7 +284,7 @@ class Test_iam_sa_user_managed_key_unused:
|
||||
assert result[1].status == "PASS"
|
||||
assert (
|
||||
result[1].status_extended
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[2].name} for Service Account {iam_client.service_accounts[0].email} was used over the last 180 days."
|
||||
== f"User-managed key {iam_client.service_accounts[0].keys[2].name} for Service Account {iam_client.service_accounts[0].email} was used over the last 30 days."
|
||||
)
|
||||
assert result[1].resource_id == iam_client.service_accounts[0].keys[2].name
|
||||
assert result[1].project_id == GCP_PROJECT_ID
|
||||
|
||||
+8
@@ -66,6 +66,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -122,6 +123,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -187,6 +189,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -252,6 +255,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -317,6 +321,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -382,6 +387,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -447,6 +453,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -512,6 +519,7 @@ class Test_iam_sa_no_administrative_privileges:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
+4
@@ -62,6 +62,7 @@ class Test_iam_sa_no_user_managed_keys:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -117,6 +118,7 @@ class Test_iam_sa_no_user_managed_keys:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -172,6 +174,7 @@ class Test_iam_sa_no_user_managed_keys:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -234,6 +237,7 @@ class Test_iam_sa_no_user_managed_keys:
|
||||
),
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
+3
@@ -62,6 +62,7 @@ class Test_iam_sa_user_managed_key_rotate_90_days:
|
||||
display_name="My service account",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -108,6 +109,7 @@ class Test_iam_sa_user_managed_key_rotate_90_days:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -166,6 +168,7 @@ class Test_iam_sa_user_managed_key_rotate_90_days:
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
from tests.providers.gcp.gcp_fixtures import (
|
||||
GCP_PROJECT_ID,
|
||||
GCP_US_CENTER1_LOCATION,
|
||||
set_mocked_gcp_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_iam_service_account_unused:
|
||||
def test_iam_no_sa(self):
|
||||
iam_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused import (
|
||||
iam_service_account_unused,
|
||||
)
|
||||
|
||||
iam_client.project_ids = [GCP_PROJECT_ID]
|
||||
iam_client.region = GCP_US_CENTER1_LOCATION
|
||||
iam_client.service_accounts = []
|
||||
|
||||
check = iam_service_account_unused()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_iam_service_account_unused_single(self):
|
||||
iam_client = mock.MagicMock()
|
||||
monitoring_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_service import (
|
||||
Key,
|
||||
ServiceAccount,
|
||||
)
|
||||
from prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused import (
|
||||
iam_service_account_unused,
|
||||
)
|
||||
|
||||
iam_client.project_ids = [GCP_PROJECT_ID]
|
||||
iam_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
iam_client.service_accounts = [
|
||||
ServiceAccount(
|
||||
name="projects/my-project/serviceAccounts/my-service-account@my-project.iam.gserviceaccount.com",
|
||||
email="my-service-account@my-project.iam.gserviceaccount.com",
|
||||
display_name="My service account",
|
||||
keys=[
|
||||
Key(
|
||||
name="90c48f61c65cd56224a12ab18e6ee9ca9c3aee7c",
|
||||
origin="GOOGLE_PROVIDED",
|
||||
type="USER_MANAGED",
|
||||
valid_after=datetime.strptime("2024-07-10", "%Y-%m-%d"),
|
||||
valid_before=datetime.strptime("9999-12-31", "%Y-%m-%d"),
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
)
|
||||
]
|
||||
|
||||
monitoring_client.sa_api_metrics = set(["111222233334444"])
|
||||
monitoring_client.audit_config = {"max_unused_account_days": 30}
|
||||
|
||||
check = iam_service_account_unused()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Service Account {iam_client.service_accounts[0].email} was used over the last 30 days."
|
||||
)
|
||||
assert result[0].resource_id == iam_client.service_accounts[0].email
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||
assert result[0].resource == iam_client.service_accounts[0]
|
||||
|
||||
def test_iam_service_account_unused_mix(self):
|
||||
iam_client = mock.MagicMock()
|
||||
monitoring_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_service import (
|
||||
Key,
|
||||
ServiceAccount,
|
||||
)
|
||||
from prowler.providers.gcp.services.iam.iam_service_account_unused.iam_service_account_unused import (
|
||||
iam_service_account_unused,
|
||||
)
|
||||
|
||||
iam_client.project_ids = [GCP_PROJECT_ID]
|
||||
iam_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
iam_client.service_accounts = [
|
||||
ServiceAccount(
|
||||
name="projects/my-project/serviceAccounts/my-service-account@my-project.iam.gserviceaccount.com",
|
||||
email="my-service-account@my-project.iam.gserviceaccount.com",
|
||||
display_name="My service account",
|
||||
keys=[
|
||||
Key(
|
||||
name="90c48f61c65cd56224a12ab18e6ee9ca9c3aee7c",
|
||||
origin="GOOGLE_PROVIDED",
|
||||
type="USER_MANAGED",
|
||||
valid_after=datetime.strptime("2024-07-10", "%Y-%m-%d"),
|
||||
valid_before=datetime.strptime("9999-12-31", "%Y-%m-%d"),
|
||||
)
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="111222233334444",
|
||||
),
|
||||
ServiceAccount(
|
||||
name="projects/my-project/serviceAccounts/my-service-account2@my-project.iam.gserviceaccount.com",
|
||||
email="my-service-account2@my-project.iam.gserviceaccount.com",
|
||||
display_name="My service account 2",
|
||||
keys=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
uniqueId="55566666777888999",
|
||||
),
|
||||
]
|
||||
|
||||
monitoring_client.sa_api_metrics = set(["111222233334444"])
|
||||
monitoring_client.audit_config = {"max_unused_account_days": 30}
|
||||
|
||||
check = iam_service_account_unused()
|
||||
result = check.execute()
|
||||
assert len(result) == 2
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Service Account {iam_client.service_accounts[0].email} was used over the last 30 days."
|
||||
)
|
||||
assert result[0].resource_id == iam_client.service_accounts[0].email
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||
assert result[0].resource == iam_client.service_accounts[0]
|
||||
|
||||
assert result[1].status == "FAIL"
|
||||
assert (
|
||||
result[1].status_extended
|
||||
== f"Service Account {iam_client.service_accounts[1].email} was not used over the last 30 days."
|
||||
)
|
||||
assert result[1].resource_id == iam_client.service_accounts[1].email
|
||||
assert result[1].project_id == GCP_PROJECT_ID
|
||||
assert result[1].location == GCP_US_CENTER1_LOCATION
|
||||
assert result[1].resource == iam_client.service_accounts[1]
|
||||
@@ -41,6 +41,7 @@ class TestIAMService:
|
||||
)
|
||||
assert iam_client.service_accounts[0].email == "service-account1@gmail.com"
|
||||
assert iam_client.service_accounts[0].display_name == "Service Account 1"
|
||||
assert iam_client.service_accounts[0].uniqueId == "111222233334444"
|
||||
assert len(iam_client.service_accounts[0].keys) == 2
|
||||
assert iam_client.service_accounts[0].keys[0].name == "key1"
|
||||
assert iam_client.service_accounts[0].keys[0].valid_after == datetime(
|
||||
@@ -66,6 +67,7 @@ class TestIAMService:
|
||||
)
|
||||
assert iam_client.service_accounts[1].email == "service-account2@gmail.com"
|
||||
assert iam_client.service_accounts[1].display_name == "Service Account 2"
|
||||
assert iam_client.service_accounts[1].uniqueId == "55566666777888999"
|
||||
assert len(iam_client.service_accounts[1].keys) == 1
|
||||
assert iam_client.service_accounts[1].keys[0].name == "key3"
|
||||
assert iam_client.service_accounts[1].keys[0].valid_after == datetime(
|
||||
|
||||
@@ -56,6 +56,7 @@ class TestMonitoringService:
|
||||
monitoring_client = Monitoring(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
monitoring_client.audit_config = {"max_unused_account_days": 30}
|
||||
assert monitoring_client.service == "monitoring"
|
||||
assert monitoring_client.project_ids == [GCP_PROJECT_ID]
|
||||
|
||||
@@ -63,3 +64,25 @@ class TestMonitoringService:
|
||||
assert "key1" in monitoring_client.sa_keys_metrics
|
||||
assert "key2" in monitoring_client.sa_keys_metrics
|
||||
assert "key3" not in monitoring_client.sa_keys_metrics
|
||||
|
||||
def test_sa_api_metrics(self):
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=mock_api_client,
|
||||
),
|
||||
):
|
||||
monitoring_client = Monitoring(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
assert monitoring_client.service == "monitoring"
|
||||
assert monitoring_client.project_ids == [GCP_PROJECT_ID]
|
||||
|
||||
assert len(monitoring_client.sa_api_metrics) == 2
|
||||
assert "111222233334444" in monitoring_client.sa_api_metrics
|
||||
assert "55566666777888999" in monitoring_client.sa_api_metrics
|
||||
assert "0000000000000" not in monitoring_client.sa_api_metrics
|
||||
|
||||
Reference in New Issue
Block a user