This check flags Amazon Neptune clusters that are encrypted with the default AWS-managed KMS key instead of a customer-managed key (CMK). Switching to a CMK gives you control over key rotation, access policies, and audit logging. The catch: you can't re-key an existing cluster in place, so the fix means a snapshot-and-restore.
Encryption at rest is a box most teams check early and never revisit. You enable it, the cluster reports "encrypted," and everyone moves on. But there's a meaningful difference between encrypted with whatever key AWS picked for you and encrypted with a key you own and govern. This check catches Neptune clusters sitting in the first category.
What this check detects
The neptune_nocmk check inspects every Amazon Neptune cluster in your account and verifies which KMS key encrypts its storage. It returns a finding when a cluster is encrypted but the key in use is the default AWS-managed key for Neptune (typically aliased aws/rds, since Neptune shares the Aurora storage engine) rather than a customer-managed key.
The distinction comes down to ownership of the KmsKeyId attached to the cluster:
- AWS-managed key (
aws/rds): created and controlled by AWS. You can't edit its key policy, you can't disable it, and rotation happens on AWS's schedule, not yours. - Customer-managed key (CMK): created in your account. You define the key policy, control who can use it, set the rotation cadence, and can disable or schedule deletion if needed.
Note: Neptune is built on the same distributed storage layer as Amazon Aurora, which is why its encryption keys show up under the aws/rds alias rather than something Neptune-specific. If you see a Neptune cluster using aws/rds, that's the AWS-managed default this check is looking for.
To see what a cluster is currently using:
aws neptune describe-db-clusters \
--query 'DBClusters[*].[DBClusterIdentifier,StorageEncrypted,KmsKeyId]' \
--output table
If StorageEncrypted is true but the KmsKeyId resolves to the aws/rds alias, you have a match for this finding.
Why it matters
The data inside a Neptune cluster is often a graph of relationships: identity links, fraud-detection signals, social connections, recommendation networks, knowledge graphs. This is exactly the kind of data that auditors and attackers both care about, and the encryption key is your last line of access control over it.
You can't restrict access to an AWS-managed key
The key policy on a customer-managed key is a real IAM boundary. You can scope kms:Decrypt to specific roles, require a particular condition, or deny access entirely during an incident. With the AWS-managed aws/rds key, you get none of that. Anyone with the right RDS/Neptune permissions can effectively read the data, and you have no key-level lever to stop them.
No independent audit trail tied to the key
CMK usage generates CloudTrail events under your control. When a CMK is used to decrypt data, you can see which principal triggered it. With the AWS-managed key, the audit story is thinner, which makes incident investigations and compliance evidence harder to assemble.
Compliance frameworks expect customer-managed keys
PCI DSS, HIPAA, SOC 2, and FedRAMP controls increasingly call for customer control over encryption keys, including the ability to rotate and revoke them on demand. An AWS-managed key rarely satisfies these requirements because the customer can't demonstrate control over the key lifecycle.
Warning: An AWS-managed key can never be used to encrypt data across accounts. If your disaster-recovery or analytics workflow needs to share a Neptune snapshot with another AWS account, you'll hit a wall. CMKs support cross-account grants; the AWS-managed key does not.
The "break glass" scenario
Suppose a leaked credential or a compromised role can read your Neptune data. With a CMK, you can update the key policy to deny kms:Decrypt for that principal and instantly cut off access to the underlying storage, buying time while you rotate the rest of your secrets. With the AWS-managed key, that lever simply does not exist.
How to fix it
Here's the part that trips people up: you cannot change the KMS key on an existing Neptune cluster. Encryption settings, including the key, are fixed at creation. Moving to a CMK means creating a new encrypted cluster from a snapshot and cutting over to it.
Step 1: Create a customer-managed key
aws kms create-key \
--description "CMK for Neptune cluster encryption" \
--tags TagKey=Purpose,TagValue=neptune-encryption
# Give it a friendly alias
aws kms create-alias \
--alias-name alias/neptune-cmk \
--target-key-id <key-id-from-previous-output>
Enable automatic annual rotation so you don't have to track it manually:
aws kms enable-key-rotation --key-id <key-id>
Step 2: Snapshot the existing cluster
aws neptune create-db-cluster-snapshot \
--db-cluster-identifier my-neptune-cluster \
--db-cluster-snapshot-identifier my-neptune-snapshot
Wait for it to finish:
aws neptune describe-db-cluster-snapshots \
--db-cluster-snapshot-identifier my-neptune-snapshot \
--query 'DBClusterSnapshots[0].Status'
Step 3: Restore the snapshot to a new cluster, re-encrypted with the CMK
The restore operation is where the re-encryption happens. Point --kms-key-id at your new CMK:
aws neptune restore-db-cluster-from-snapshot \
--db-cluster-identifier my-neptune-cluster-cmk \
--snapshot-identifier my-neptune-snapshot \
--engine neptune \
--kms-key-id alias/neptune-cmk
Then add at least one instance to the restored cluster so it's queryable:
aws neptune create-db-instance \
--db-instance-identifier my-neptune-cmk-instance-1 \
--db-cluster-identifier my-neptune-cluster-cmk \
--engine neptune \
--db-instance-class db.r5.large
Step 4: Cut over and decommission the old cluster
Update your application's connection endpoint to point at the new cluster, verify reads and writes, then remove the old one once you're confident.
Danger: Deleting a Neptune cluster is irreversible. Confirm your application is fully cut over and take a final snapshot before running this. The command below uses --skip-final-snapshot only as an example for a cluster you have already backed up.
# Delete instances first
aws neptune delete-db-instance \
--db-instance-identifier my-neptune-instance-1
# Then the cluster (take a final snapshot for safety)
aws neptune delete-db-cluster \
--db-cluster-identifier my-neptune-cluster \
--final-db-cluster-snapshot-identifier my-neptune-final-snapshot
Warning: The cutover involves downtime unless you plan around it. The new cluster restored from a snapshot won't contain writes that happened after the snapshot was taken. For zero-data-loss migrations, stop writes before snapshotting, or use a maintenance window where you can briefly pause the application.
Doing it right the first time with Terraform
For new clusters, set the CMK at creation so you never end up in the snapshot-restore situation:
resource "aws_kms_key" "neptune" {
description = "CMK for Neptune encryption"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_kms_alias" "neptune" {
name = "alias/neptune-cmk"
target_key_id = aws_kms_key.neptune.key_id
}
resource "aws_neptune_cluster" "main" {
cluster_identifier = "my-neptune-cluster"
engine = "neptune"
storage_encrypted = true
kms_key_arn = aws_kms_key.neptune.arn
skip_final_snapshot = false
}
Tip: Reuse a single, well-governed CMK per environment rather than one key per cluster. It keeps your key policies manageable and your CloudTrail analysis simpler, while still giving you the control benefits a CMK provides.
How to prevent it from happening again
Because Neptune encryption can't be changed after creation, the cheapest place to catch this is before the cluster ever exists. Push the check left into your pipeline.
Block it in CI with policy-as-code
An OPA/Conftest policy against Terraform plans catches a missing CMK before apply:
package neptune
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_neptune_cluster"
not resource.change.after.kms_key_arn
msg := sprintf("Neptune cluster '%s' must specify a customer-managed kms_key_arn", [resource.address])
}
The same idea works with Checkov's built-in checks for RDS/Neptune encryption keys, or with an AWS Config rule that flags clusters not using an approved CMK ARN.
Detect drift continuously
Policy-as-code only covers infrastructure that flows through your pipeline. Resources created by hand, by a vendor tool, or in a forgotten account slip past it. A continuous scan across all accounts and regions is what closes that gap, which is exactly what the neptune_nocmk check is for in Lensix.
Tip: Pair the pipeline gate with scheduled scanning. The gate prevents new violations; the scan finds the clusters that were spun up before you had the gate, or outside it entirely.
Best practices
- Set the CMK at creation, always. Since the key is immutable, treating CMK encryption as a hard default for every new Neptune cluster saves you the migration pain later.
- Enable automatic key rotation. Annual rotation on the CMK is a single flag and keeps you aligned with most compliance frameworks at no operational cost.
- Scope the key policy tightly. Grant
kms:Decryptonly to the roles that actually need to run Neptune workloads. The whole point of a CMK is that you can do this. - Tag your keys. A clear
PurposeandEnvironmenttag on each CMK makes audits and cost allocation far easier later on. - Document the key lifecycle. Auditors will ask who can rotate, disable, and delete the key. Having that written down turns a CMK from a checkbox into evidence of real control.
- Watch for cross-account needs early. If you anticipate sharing snapshots across accounts, configure the CMK key policy for those grants from the start rather than discovering the limitation mid-incident.
Encryption at rest with an AWS-managed key is not nothing, but it leaves the most useful controls off the table. Moving Neptune to a customer-managed key turns encryption from a passive checkbox into an active security boundary you actually own. Do it at creation, gate it in CI, and scan for the stragglers.

