This check flags Cloud KMS keys that lack automatic rotation or rotate less often than every 90 days. Stale keys mean a single leaked key version can decrypt your data indefinitely, so set a rotation period of 90 days or less with gcloud kms keys update --rotation-period=90d --next-rotation-time=....
Encryption keys are not "set and forget" assets. The longer a single key version stays active, the more data it protects and the bigger the blast radius if that version is ever compromised. Cloud KMS supports automatic rotation for symmetric keys, and Lensix raises kms_norotation when a key either has no rotation schedule or rotates on a cadence longer than 90 days.
This post walks through what the check looks at, why a 90 day window is a sensible default, and how to fix and prevent the issue across CLI, console, and Terraform.
What this check detects
The kms_norotation check inspects each Cloud KMS CryptoKey of purpose ENCRYPT_DECRYPT and evaluates two things:
- Whether
rotationPeriodis set at all. If it is missing, the key never rotates automatically. - Whether the configured
rotationPeriodexceeds 90 days. Anything longer is flagged as too infrequent.
When you enable rotation, Cloud KMS automatically generates a fresh key version on the schedule you define and promotes it to be the new primary. Older versions stay around so they can still decrypt data that was encrypted with them, but all new encryption uses the latest version.
Note: Automatic rotation only applies to symmetric ENCRYPT_DECRYPT keys. Asymmetric signing and encryption keys, and keys backed by external or hardware managers in certain configurations, do not support automatic rotation and must be rotated manually. Lensix scopes this check to keys where rotation is actually supported.
Why it matters
Key rotation limits the value of any single key version to an attacker and shrinks the window of exposure if a key is leaked, mishandled, or accessed through an over-permissioned service account.
Consider a realistic scenario. A service account with cloudkms.cryptoKeyVersions.useToDecrypt is compromised through a leaked credential in a build log. If your key has rotated regularly, only data encrypted with recently active versions is at immediate risk, and you can disable or destroy specific versions to contain the incident. If the same key version has been primary for two years, every object, database backup, and secret encrypted in that period shares one fate.
There are three concrete reasons this matters in practice:
- Smaller blast radius. Frequent rotation means less data is tied to any one key version, so containment and re-encryption are more surgical.
- Cryptographic hygiene. Limiting the volume of data encrypted under a single key reduces exposure to cryptanalytic risk over long timeframes.
- Compliance expectations. CIS Google Cloud Foundations Benchmark recommends a rotation period of 90 days or less. PCI DSS, SOC 2, and similar frameworks expect documented, enforced key lifecycle management.
Warning: Rotation does not re-encrypt existing data. Old key versions remain enabled so they can decrypt previously encrypted ciphertext. Rotating a key reduces future exposure, but if you suspect a version is compromised you still need to re-encrypt affected data and then disable or destroy the bad version.
How to fix it
You can enable or tighten rotation without disrupting existing data. Setting a rotation period takes effect going forward, and KMS handles version promotion automatically.
Option 1: gcloud CLI
First, confirm the current state of the key:
gcloud kms keys describe my-key \
--location=us-central1 \
--keyring=my-keyring \
--format="yaml(name, rotationPeriod, nextRotationTime)"
If rotationPeriod is absent or larger than 7776000s (90 days), update it:
gcloud kms keys update my-key \
--location=us-central1 \
--keyring=my-keyring \
--rotation-period=90d \
--next-rotation-time="$(date -u -d '+7 days' +%Y-%m-%dT%H:%M:%SZ)"
The --next-rotation-time sets when the first rotation happens. Scheduling it a few days out gives you a buffer before the cadence kicks in. After this, KMS rotates every 90 days automatically.
Note: The --rotation-period flag accepts shorthand like 90d or a raw seconds value like 7776000s. The minimum allowed period is 24 hours and the maximum is roughly 100 years, but for this check you want 90 days or less.
Option 2: Google Cloud Console
- Go to Security → Key Management.
- Select the key ring, then click the key you want to update.
- Click Edit rotation period.
- Set the period to 90 days or less and choose a starting date.
- Save. The new schedule applies immediately.
Option 3: Terraform
If you manage KMS with Terraform, set rotation_period on the google_kms_crypto_key resource. The value must be in seconds with an s suffix.
resource "google_kms_key_ring" "main" {
name = "my-keyring"
location = "us-central1"
}
resource "google_kms_crypto_key" "main" {
name = "my-key"
key_ring = google_kms_key_ring.main.id
purpose = "ENCRYPT_DECRYPT"
rotation_period = "7776000s" # 90 days
lifecycle {
prevent_destroy = true
}
}
Danger: Never destroy or recreate a KMS key that is protecting live data. A destroyed key version cannot decrypt anything encrypted with it, and that data is permanently unrecoverable. Always use prevent_destroy = true on production keys, and confirm a terraform plan shows an in-place update rather than a replacement before applying.
How to prevent it from happening again
Fixing a single key is easy. Keeping every new key compliant requires guardrails that run before deployment and continuously after.
Enforce rotation in CI with policy-as-code
Use Open Policy Agent with Conftest to fail any Terraform plan that creates a KMS key without a rotation period of 90 days or less.
package main
max_rotation_seconds := 7776000 # 90 days
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_kms_crypto_key"
after := resource.change.after
not after.rotation_period
msg := sprintf("KMS key '%s' has no rotation_period set", [after.name])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_kms_crypto_key"
after := resource.change.after
period := to_number(trim_suffix(after.rotation_period, "s"))
period > max_rotation_seconds
msg := sprintf("KMS key '%s' rotation_period %vs exceeds 90 days", [after.name, period])
}
Wire it into your pipeline against a plan output:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json --policy policy/
Tip: If you run GKE or other GCP workloads under Anthos, you can enforce the same rule at the platform level with Policy Controller constraints, so the rule applies even to changes made outside your CI pipeline.
Catch drift continuously
Policy gates only cover infrastructure that flows through your pipeline. Keys created manually in the console, or rotation periods loosened during an incident and never reset, slip past CI. Run a scheduled scan so existing keys are evaluated on an ongoing basis, which is exactly what the Lensix kms_norotation check does across all your projects and regions.
Best practices
- Default to 90 days or less. This aligns with CIS recommendations and is a reasonable balance between security and operational overhead. For especially sensitive workloads, consider 30 days.
- Use separate keys per environment and data domain. A dedicated key for production database backups, another for application secrets, and so on. This narrows blast radius and makes incident response cleaner.
- Apply least privilege on key usage. Grant
roles/cloudkms.cryptoKeyEncrypterDecrypteronly to the service accounts that need it, and keeproles/cloudkms.admintightly held. Rotation does not help if many principals can use the key freely. - Monitor key access with Cloud Audit Logs. Enable Data Access logs for Cloud KMS so you can detect unusual decrypt activity and respond before it becomes a breach.
- Re-encrypt after suspected compromise. Rotation alone leaves old versions enabled. If a version is compromised, re-encrypt affected data with the new primary, then disable and eventually destroy the bad version.
- Document your key lifecycle. Auditors will ask who owns each key, what it protects, and how rotation is enforced. A short ownership table tied to your IaC saves time during reviews.
Enabling rotation is one of the lowest-effort, highest-value hardening steps you can take in Cloud KMS. It costs nothing extra, applies without downtime, and meaningfully shrinks the damage a leaked key can do. Set the schedule once, enforce it in CI, and let continuous scanning catch the keys that slip through.

