feat(gcp): add secretmanager_secret_rotation_enabled check (#11026)

Co-authored-by: Lydia Vilchez <lydiavilchezlopez@gmail.com>
This commit is contained in:
s1ns3nz0
2026-06-23 18:30:15 +09:00
committed by GitHub
parent c6c07957a6
commit 48acb3bd2e
10 changed files with 589 additions and 0 deletions
+1
View File
@@ -92,6 +92,7 @@ class TestGCPProvider:
"shodan_api_key": None,
"max_unused_account_days": 180,
"storage_min_retention_days": 90,
"secretmanager_max_rotation_days": 90,
"mig_min_zones": 2,
"max_snapshot_age_days": 90,
}
@@ -0,0 +1,385 @@
from unittest import mock
from tests.providers.gcp.gcp_fixtures import (
GCP_PROJECT_ID,
set_mocked_gcp_provider,
)
_CHECK_PATH = (
"prowler.providers.gcp.services.secretmanager."
"secretmanager_secret_rotation_enabled."
"secretmanager_secret_rotation_enabled"
)
_CLIENT_PATH = f"{_CHECK_PATH}.secretmanager_client"
def _secret_id(name: str) -> str:
return f"projects/{GCP_PROJECT_ID}/secrets/{name}"
class Test_secretmanager_secret_rotation_enabled:
def test_no_secrets(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
secretmanager_client.secrets = []
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 0
def test_rotation_within_90_days_pass(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-rotated"),
name="secret-rotated",
project_id=GCP_PROJECT_ID,
rotation_period="7776000s",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Secret secret-rotated has automatic rotation enabled with a period of 90 days."
)
assert result[0].resource_id == "secret-rotated"
assert result[0].resource_name == "secret-rotated"
assert result[0].location == "global"
assert result[0].project_id == GCP_PROJECT_ID
def test_rotation_period_exceeds_max_fail(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-stale"),
name="secret-stale",
project_id=GCP_PROJECT_ID,
rotation_period="9504000s",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "exceeds the 90-day maximum" in result[0].status_extended
assert result[0].resource_id == "secret-stale"
def test_rotation_period_one_second_over_max_fail(self):
"""90 days + 1 second must fail — comparison is on seconds, not floored days."""
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
# 90 days = 7_776_000 seconds. Add 1 second.
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-90d-plus-1s"),
name="secret-90d-plus-1s",
project_id=GCP_PROJECT_ID,
rotation_period="7776001s",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "exceeds the 90-day maximum" in result[0].status_extended
def test_no_rotation_fail(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-no-rotation"),
name="secret-no-rotation",
project_id=GCP_PROJECT_ID,
rotation_period=None,
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Secret secret-no-rotation does not have automatic rotation enabled."
)
def test_fractional_seconds_period_pass(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-fractional"),
name="secret-fractional",
project_id=GCP_PROJECT_ID,
rotation_period="2592000.500000000s",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_sub_day_rotation_period_pass(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-sub-day"),
name="secret-sub-day",
project_id=GCP_PROJECT_ID,
rotation_period="3600s",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_overdue_next_rotation_fail(self):
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-overdue"),
name="secret-overdue",
project_id=GCP_PROJECT_ID,
rotation_period="7776000s",
next_rotation_time="2020-01-01T00:00:00Z",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "overdue" in result[0].status_extended
def test_invalid_rotation_period_format_fail(self):
"""Unparseable rotation_period falls back to None → FAIL with no-rotation message."""
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-bad-period"),
name="secret-bad-period",
project_id=GCP_PROJECT_ID,
rotation_period="not-a-duration",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Secret secret-bad-period does not have automatic rotation enabled."
)
def test_invalid_next_rotation_time_fail_closed(self):
"""Unparseable next_rotation_time fails closed → FAIL as overdue."""
secretmanager_client = mock.MagicMock()
secretmanager_client.audit_config = {"secretmanager_max_rotation_days": 90}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
_CLIENT_PATH,
new=secretmanager_client,
),
):
from prowler.providers.gcp.services.secretmanager.secretmanager_secret_rotation_enabled.secretmanager_secret_rotation_enabled import (
secretmanager_secret_rotation_enabled,
)
from prowler.providers.gcp.services.secretmanager.secretmanager_service import (
Secret,
)
secretmanager_client.secrets = [
Secret(
id=_secret_id("secret-bad-timestamp"),
name="secret-bad-timestamp",
project_id=GCP_PROJECT_ID,
rotation_period="7776000s",
next_rotation_time="not-a-timestamp",
)
]
check = secretmanager_secret_rotation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "overdue" in result[0].status_extended
@@ -60,8 +60,77 @@ class TestSecretManagerService:
assert secret.id == f"projects/{GCP_PROJECT_ID}/secrets/my-secret"
assert secret.project_id == GCP_PROJECT_ID
assert secret.location == "global"
assert secret.rotation_period is None
assert secret.next_rotation_time is None
assert secret.publicly_accessible is False
def test_get_secrets_with_rotation(self):
def mock_api_client(*args, **kwargs):
return _make_secretmanager_client(
secrets_list=[
{
"name": f"projects/{GCP_PROJECT_ID}/secrets/secret-with-rotation",
"rotation": {
"rotationPeriod": "7776000s",
"nextRotationTime": "2026-09-01T00:00:00Z",
},
}
]
)
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,
),
):
sm_client = SecretManager(
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
)
assert len(sm_client.secrets) == 1
secret = sm_client.secrets[0]
assert secret.name == "secret-with-rotation"
assert secret.rotation_period == "7776000s"
assert secret.next_rotation_time == "2026-09-01T00:00:00Z"
def test_get_secrets_with_null_rotation(self):
"""API returning explicit `rotation: null` must not break enumeration."""
def mock_api_client(*args, **kwargs):
return _make_secretmanager_client(
secrets_list=[
{
"name": f"projects/{GCP_PROJECT_ID}/secrets/null-rotation",
"rotation": None,
}
]
)
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,
),
):
sm_client = SecretManager(
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
)
assert len(sm_client.secrets) == 1
secret = sm_client.secrets[0]
assert secret.name == "null-rotation"
assert secret.rotation_period is None
assert secret.next_rotation_time is None
def test_get_secrets_iam_policy_all_users(self):
def mock_api_client(*args, **kwargs):
return _make_secretmanager_client(