fix(gcp): pass iam_service_account_unused for disabled service accounts (#11467)

This commit is contained in:
Aline Almeida
2026-06-05 12:07:30 +02:00
committed by GitHub
parent 3a3d9d6146
commit a5bc226f11
4 changed files with 66 additions and 1 deletions
+1
View File
@@ -16,6 +16,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- GCP `logging_sink_created` now recognizes organization-level aggregated sinks with `includeChildren=True`, avoiding false failures for covered projects [(#11355)](https://github.com/prowler-cloud/prowler/pull/11355)
- GCP `iam_service_account_unused` now passes disabled service accounts instead of failing them, since a disabled account cannot authenticate or be used [(#11467)](https://github.com/prowler-cloud/prowler/pull/11467)
---
@@ -37,6 +37,7 @@ class IAM(GCPService):
display_name=account.get("displayName", ""),
project_id=project_id,
uniqueId=account.get("uniqueId", ""),
disabled=account.get("disabled", False),
)
)
@@ -102,6 +103,7 @@ class ServiceAccount(BaseModel):
keys: list[Key] = []
project_id: str
uniqueId: str
disabled: bool = False
class AccessApproval(GCPService):
@@ -19,7 +19,12 @@ class iam_service_account_unused(Check):
resource_id=account.email,
location=iam_client.region,
)
if account.uniqueId in sa_ids_used:
if account.disabled:
report.status = "PASS"
report.status_extended = (
f"Service Account {account.email} is disabled and cannot be used."
)
elif 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:
@@ -179,3 +179,60 @@ class Test_iam_service_account_unused:
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]
def test_iam_service_account_disabled(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 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/disabled-sa@my-project.iam.gserviceaccount.com",
email="disabled-sa@my-project.iam.gserviceaccount.com",
display_name="Disabled service account",
keys=[],
project_id=GCP_PROJECT_ID,
uniqueId="999888877776666",
disabled=True,
)
]
# The account is absent from the usage metrics, so a non-disabled
# account here would FAIL. Being disabled must take precedence and
# PASS, since a disabled account cannot authenticate or be used.
monitoring_client.sa_api_metrics = set()
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} is disabled and cannot be used."
)
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]