Back to Checks
gcp

Secret Manager secret is rotated every 90 days or less

secretmanager_secret_rotation_enabled
Service: secretmanagerSeverity: medium
OfficialBuilt-inVerified
Categories:secrets
Resource Type:secretmanager.googleapis.com/Secret

Secret Manager secrets have automatic rotation configured with a rotation period of 90 days or less and the next scheduled rotation has not been missed.

The evaluation reviews each secret's rotation settings to confirm both the period and the upcoming rotation time are within bounds.

Risk

Without timely rotation, a leaked or compromised secret remains valid indefinitely, eroding confidentiality and widening the blast radius of any credential exposure. Stale secrets also bypass periodic re-authorization controls expected by frameworks such as PCI-DSS and ISO 27001.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler gcp --checks secretmanager_secret_rotation_enabled

Recommendation

Enable automatic rotation for every Secret Manager secret with a period of 90 days or less.

Wire Pub/Sub notifications to trigger rotation logic and apply defense in depth by versioning each secret update so consumers can roll back without losing availability.

Remediation

CLI

gcloud secrets update <SECRET_NAME> --rotation-period=7776000s --next-rotation-time=<RFC3339_TIMESTAMP>

Terraform

resource "google_secret_manager_secret" "<example_resource_name>" {
  secret_id = "<example_resource_id>"

  replication {
    auto {}
  }

  rotation {
    rotation_period    = "7776000s" # Critical: enables rotation within 90 days
    next_rotation_time = "<RFC3339_TIMESTAMP>"
  }
}

Other

1In Google Cloud Console, go to Security > Secret Manager
2Select the secret and click Edit secret
3Under Rotation, enable Automatic rotation with a period of 90 days or less
4Configure a Pub/Sub topic to receive rotation notifications
5Click Save

Check Code

import datetime

from prowler.lib.check.models import Check, Check_Report_GCP
from prowler.providers.gcp.services.secretmanager.secretmanager_client import (
    secretmanager_client,
)


class secretmanager_secret_rotation_enabled(Check):
    """
    Ensure Secret Manager secrets have automatic rotation configured within the max rotation period.

    - PASS: Secret has a rotation period within the maximum (default 90 days) and the next rotation is not overdue.
    - FAIL: Secret has no rotation, the period exceeds the maximum, or the next rotation has been missed.
    """

    def execute(self) -> list[Check_Report_GCP]:
        """Evaluate every Secret Manager secret's rotation configuration against the maximum rotation period."""
        findings = []

        max_rotation_days = int(
            getattr(secretmanager_client, "audit_config", {}).get(
                "secretmanager_max_rotation_days", 90
            )
        )

        for secret in secretmanager_client.secrets:
            report = Check_Report_GCP(
                metadata=self.metadata(),
                resource=secret,
                resource_id=secret.name,
            )

            rotation_seconds = None
            if secret.rotation_period:
                try:
                    rotation_seconds = float(secret.rotation_period[:-1])
                except (ValueError, IndexError):
                    rotation_seconds = None

            rotation_overdue = False
            if rotation_seconds is not None and secret.next_rotation_time:
                try:
                    parsed = secret.next_rotation_time.replace("Z", "+00:00")
                    next_rotation_time = datetime.datetime.fromisoformat(parsed)
                    rotation_overdue = next_rotation_time < datetime.datetime.now(
                        datetime.timezone.utc
                    )
                except (ValueError, AttributeError):
                    rotation_overdue = True

            max_rotation_seconds = max_rotation_days * 86400
            rotation_days = (
                int(rotation_seconds // 86400) if rotation_seconds is not None else None
            )

            if rotation_seconds is None:
                report.status = "FAIL"
                report.status_extended = (
                    f"Secret {secret.name} does not have automatic rotation enabled."
                )
            elif rotation_seconds > max_rotation_seconds:
                report.status = "FAIL"
                report.status_extended = (
                    f"Secret {secret.name} has rotation enabled but the period "
                    f"({rotation_days} days) exceeds the {max_rotation_days}-day maximum."
                )
            elif rotation_overdue:
                report.status = "FAIL"
                report.status_extended = (
                    f"Secret {secret.name} has rotation configured "
                    f"({rotation_days} days) but the scheduled rotation is overdue."
                )
            else:
                report.status = "PASS"
                report.status_extended = (
                    f"Secret {secret.name} has automatic rotation enabled "
                    f"with a period of {rotation_days} days."
                )

            findings.append(report)

        return findings