This check flags Kinesis data streams encrypted with the default AWS-managed KMS key instead of a customer-managed key (CMK). Switch to a CMK so you control key rotation, access policies, and audit logging. Fix it in one call with aws kinesis start-stream-encryption pointing at your own key.
Kinesis data streams carry some of the most sensitive data in a typical AWS environment: clickstreams, application logs, financial events, IoT telemetry, change data capture from databases. By default, server-side encryption on a Kinesis stream uses the AWS-managed key aws/kinesis. That is better than nothing, but it leaves you with very little control over how the key is governed.
The kinesis_defaultkms check finds streams that rely on this default key and flags them so you can move to a customer-managed key (CMK).
What this check detects
The check inspects each Kinesis data stream in your account and looks at its encryption configuration. Specifically, it reports a stream as non-compliant when:
- Server-side encryption is enabled (good), but
- The
KeyIdresolves to the AWS-managedaws/kinesiskey rather than a CMK you created.
You can see this yourself with the describe-stream-summary call:
aws kinesis describe-stream-summary \
--stream-name orders-events \
--query 'StreamDescriptionSummary.{Encryption:EncryptionType,KeyId:KeyId}'
A stream on the default key returns something like this:
{
"Encryption": "KMS",
"KeyId": "alias/aws/kinesis"
}
A compliant stream points at a CMK by ARN or alias:
{
"Encryption": "KMS",
"KeyId": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
}
Note: The aws/kinesis key is created and owned by AWS in your account. You cannot edit its key policy, you cannot disable it, and you cannot delete it. It exists purely so encryption "just works" without setup.
Why it matters
Encryption at rest with the default key checks a compliance box, but it strips you of the controls that actually make KMS useful. Here is what you give up.
You cannot scope access with a key policy
With a CMK, the key policy is a second gate in front of your data. Even if an IAM principal has kinesis:GetRecords, it still needs kms:Decrypt on the specific CMK to read encrypted records. The AWS-managed key grants decrypt access to any principal in the account that is already allowed to use Kinesis, so there is no independent layer to lock down. A CMK lets you say "only the analytics role and the consumer service can decrypt this stream."
You do not control rotation
AWS-managed keys rotate on AWS's schedule and you have no say in it. With a CMK you choose annual automatic rotation, or you rotate manually on your own cadence to meet internal policy or regulatory requirements.
Weaker audit and blast-radius isolation
Every CMK has its own CloudTrail footprint and its own ARN. That makes it straightforward to alert on unusual Decrypt activity for a specific sensitive stream, and to revoke or disable a single key during an incident without affecting unrelated workloads. The shared default key cannot be disabled, so you have no break-glass control.
Warning: Many frameworks (PCI DSS, HIPAA, SOC 2, FedRAMP) expect that encryption keys protecting regulated data are customer-managed so that key access and lifecycle are auditable. The default key often fails these controls during an audit even though "encryption" is technically on.
Cross-account sharing breaks
If you ever share a stream with a consumer in another account, the AWS-managed key cannot be used across accounts. A CMK with the right key policy can. Defaulting now means a painful migration later.
How to fix it
The fix has two parts: create (or pick) a CMK, then point the stream at it.
Step 1: Create a customer-managed key
aws kms create-key \
--description "CMK for Kinesis stream orders-events" \
--tags TagKey=team,TagValue=data-platform \
--query 'KeyMetadata.KeyId' --output text
Give it a readable alias so your IaC and operators do not deal in raw key IDs:
aws kms create-alias \
--alias-name alias/kinesis-orders-events \
--target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
Then attach a key policy that grants kms:Decrypt, kms:GenerateDataKey, and the related Kinesis grant operations only to the principals that need them. A minimal policy statement for a consumer role looks like this:
{
"Sid": "AllowKinesisConsumers",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/orders-consumer"
},
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey",
"kms:CreateGrant"
],
"Resource": "*"
}
Step 2: Enable encryption with the CMK
This is the call that actually changes the stream. It works the same whether the stream currently uses the default key or no encryption at all.
Warning: Re-encrypting a stream applies to new records going forward. Records already in the stream keep their previous encryption until they age out per your retention period. There is no rewrite of existing data, and there is no downtime, but producers and consumers must have permission on the new CMK before you switch or they will start failing.
aws kinesis start-stream-encryption \
--stream-name orders-events \
--encryption-type KMS \
--key-id alias/kinesis-orders-events
Verify it took effect:
aws kinesis describe-stream-summary \
--stream-name orders-events \
--query 'StreamDescriptionSummary.KeyId'
It returns the CMK ARN instead of alias/aws/kinesis.
Terraform
If you manage streams as code, set the key explicitly so the default is never used:
resource "aws_kms_key" "kinesis" {
description = "CMK for Kinesis orders-events"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_kms_alias" "kinesis" {
name = "alias/kinesis-orders-events"
target_key_id = aws_kms_key.kinesis.key_id
}
resource "aws_kinesis_stream" "orders_events" {
name = "orders-events"
shard_count = 2
retention_period = 48
encryption_type = "KMS"
kms_key_id = aws_kms_alias.kinesis.name
}
Tip: Use the alias (alias/...) rather than the raw key ID in both CLI and Terraform. If you ever need to roll to a new key, you update the alias target once instead of hunting down every reference.
How to prevent it from happening again
Fixing the streams you have is the easy half. Stopping new ones from shipping on the default key is what keeps the finding from coming back.
Block it in CI with policy-as-code
If you use Terraform, a Checkov or OPA gate in your pipeline catches missing or default keys before apply. Checkov already ships the relevant policy:
checkov -d . --check CKV_AWS_43
For a custom OPA/Conftest rule against a Terraform plan:
package terraform.kinesis
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_kinesis_stream"
not resource.change.after.kms_key_id
msg := sprintf("Kinesis stream '%s' must set a customer-managed kms_key_id", [resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_kinesis_stream"
resource.change.after.kms_key_id == "alias/aws/kinesis"
msg := sprintf("Kinesis stream '%s' uses the default AWS-managed key", [resource.address])
}
Detect drift in the account
AWS Config has a managed rule, kinesis-stream-encrypted, that confirms encryption is on. Pair it with a CMK requirement so the default key is treated as non-compliant. Lensix runs the equivalent kinesis_defaultkms check continuously, so a stream that drifts back to the default key surfaces without you scheduling anything.
Tip: Add a guardrail SCP that allows kinesis:CreateStream only when the request includes a CMK key ID. This stops the default-key path at the API level across every account in the org, which is far harder to bypass than a CI check alone.
Best practices
- One CMK per data domain, not per stream. A key per environment or per sensitivity tier keeps key policies manageable while still giving you isolation. A separate key for every stream becomes hard to govern fast.
- Enable automatic rotation on the CMK and document the cadence. KMS handles the backing material transparently, so consumers never notice.
- Keep key policies least-privilege. Grant
kms:Decryptonly to the consumer roles andkms:GenerateDataKeyonly to producers. Avoid wildcard principals. - Encrypt at creation, never retrofit by hand. Set the key in your module defaults so new streams inherit it automatically.
- Alarm on key activity. A CloudWatch alarm on unusual
kms:Decryptvolume for a sensitive stream's key is one of the cheapest early-warning signals you can add.
Note: Customer-managed KMS keys cost roughly $1 per key per month plus per-request charges. Kinesis batches encryption operations efficiently, so for most workloads the request cost is small. Budget for the keys, not for surprise per-record fees.
Moving off the default key is one of those changes that takes a single API call but quietly upgrades your security posture: real access control, real audit trails, and a key you can actually disable when something goes wrong. Make the CMK the default in your tooling and this finding stops appearing for good.

