|
| 1 | +import datetime |
| 2 | + |
| 3 | +from prowler.lib.check.models import Check, Check_Report_GCP |
| 4 | +from prowler.providers.gcp.services.secretmanager.secretmanager_client import ( |
| 5 | + secretmanager_client, |
| 6 | +) |
| 7 | + |
| 8 | + |
| 9 | +class secretmanager_secret_rotation_enabled(Check): |
| 10 | + """ |
| 11 | + Ensure Secret Manager secrets have automatic rotation configured within the max rotation period. |
| 12 | +
|
| 13 | + - PASS: Secret has a rotation period within the maximum (default 90 days) and the next rotation is not overdue. |
| 14 | + - FAIL: Secret has no rotation, the period exceeds the maximum, or the next rotation has been missed. |
| 15 | + """ |
| 16 | + |
| 17 | + def execute(self) -> list[Check_Report_GCP]: |
| 18 | + """Evaluate every Secret Manager secret's rotation configuration against the maximum rotation period.""" |
| 19 | + findings = [] |
| 20 | + |
| 21 | + max_rotation_days = int( |
| 22 | + getattr(secretmanager_client, "audit_config", {}).get( |
| 23 | + "secretmanager_max_rotation_days", 90 |
| 24 | + ) |
| 25 | + ) |
| 26 | + |
| 27 | + for secret in secretmanager_client.secrets: |
| 28 | + report = Check_Report_GCP( |
| 29 | + metadata=self.metadata(), |
| 30 | + resource=secret, |
| 31 | + resource_id=secret.name, |
| 32 | + ) |
| 33 | + |
| 34 | + rotation_seconds = None |
| 35 | + if secret.rotation_period: |
| 36 | + try: |
| 37 | + rotation_seconds = float(secret.rotation_period[:-1]) |
| 38 | + except (ValueError, IndexError): |
| 39 | + rotation_seconds = None |
| 40 | + |
| 41 | + rotation_overdue = False |
| 42 | + if rotation_seconds is not None and secret.next_rotation_time: |
| 43 | + try: |
| 44 | + parsed = secret.next_rotation_time.replace("Z", "+00:00") |
| 45 | + next_rotation_time = datetime.datetime.fromisoformat(parsed) |
| 46 | + rotation_overdue = next_rotation_time < datetime.datetime.now( |
| 47 | + datetime.timezone.utc |
| 48 | + ) |
| 49 | + except (ValueError, AttributeError): |
| 50 | + rotation_overdue = True |
| 51 | + |
| 52 | + max_rotation_seconds = max_rotation_days * 86400 |
| 53 | + rotation_days = ( |
| 54 | + int(rotation_seconds // 86400) if rotation_seconds is not None else None |
| 55 | + ) |
| 56 | + |
| 57 | + if rotation_seconds is None: |
| 58 | + report.status = "FAIL" |
| 59 | + report.status_extended = ( |
| 60 | + f"Secret {secret.name} does not have automatic rotation enabled." |
| 61 | + ) |
| 62 | + elif rotation_seconds > max_rotation_seconds: |
| 63 | + report.status = "FAIL" |
| 64 | + report.status_extended = ( |
| 65 | + f"Secret {secret.name} has rotation enabled but the period " |
| 66 | + f"({rotation_days} days) exceeds the {max_rotation_days}-day maximum." |
| 67 | + ) |
| 68 | + elif rotation_overdue: |
| 69 | + report.status = "FAIL" |
| 70 | + report.status_extended = ( |
| 71 | + f"Secret {secret.name} has rotation configured " |
| 72 | + f"({rotation_days} days) but the scheduled rotation is overdue." |
| 73 | + ) |
| 74 | + else: |
| 75 | + report.status = "PASS" |
| 76 | + report.status_extended = ( |
| 77 | + f"Secret {secret.name} has automatic rotation enabled " |
| 78 | + f"with a period of {rotation_days} days." |
| 79 | + ) |
| 80 | + |
| 81 | + findings.append(report) |
| 82 | + |
| 83 | + return findings |
0 commit comments