Back to blog
AWSBest PracticesCloud SecurityOperations & ComplianceStorage

Kinesis Stream Not Encrypted: Why It Matters and How to Fix It

Learn why unencrypted Kinesis Data Streams fail compliance, the real risk of plaintext records at rest, and how to enable KMS server-side encryption fast.

TL;DR

This check flags Kinesis Data Streams that are sending records to disk without server-side encryption. Anything written to the stream sits unencrypted at rest, which fails most compliance baselines. Fix it by enabling SSE with a KMS key using a single start-stream-encryption call.

Kinesis Data Streams quietly move a lot of sensitive data. Clickstreams, application logs, financial transactions, IoT telemetry, change-data-capture feeds from your databases. All of it lands on Kinesis shards and is persisted to disk for the duration of your retention window, which can be up to 365 days. If server-side encryption is off, those records sit in plaintext on AWS-managed storage.

The kinesis_unencrypted check catches exactly this: a stream where server-side encryption (SSE) has never been turned on, or was turned off at some point. It is one of the easiest misconfigurations to fix and one of the easiest to overlook, because Kinesis does not encrypt streams by default when you create them through the API or older IaC templates.


What this check detects

Every Kinesis Data Stream has an EncryptionType attribute. It is either NONE or KMS. This check fails any stream where the value is NONE, meaning records are written to disk without encryption.

You can see the current state with a quick describe call:

aws kinesis describe-stream-summary \
  --stream-name my-event-stream \
  --query 'StreamDescriptionSummary.{Name:StreamName,Encryption:EncryptionType,KeyId:KeyId}'

If the response shows "Encryption": "NONE", the stream is unencrypted and the check fails.

Note: Kinesis SSE is transparent. Producers and consumers do not change their code. Encryption and decryption happen on the AWS side as records are written to and read from disk. The only requirement is that the IAM roles producing and consuming have permission to use the KMS key.


Why it matters

Encryption at rest is a baseline control, not a nice-to-have. A few concrete reasons this check is worth acting on:

  • Compliance gaps. PCI DSS, HIPAA, SOC 2, and FedRAMP all expect data at rest to be encrypted. An unencrypted stream carrying cardholder data or PHI is a finding in any audit.
  • Defense in depth. SSE with a customer-managed KMS key means access to the data requires both Kinesis permissions and KMS key permissions. Without it, anyone with Kinesis read access on the stream gets the raw records, and there is no second gate.
  • Blast radius on key data flows. Streams often aggregate data from many sources. A single stream might carry events from your entire fleet. If that data is logged unencrypted, one over-permissive IAM policy exposes everything flowing through it.
  • Retention extends the exposure. A record is not transient. With a long retention period, plaintext data persists on disk for days or months, widening the window for any access-control mistake.

The common real-world scenario is not a dramatic breach. It is an auditor pulling your resource inventory, finding a production stream with EncryptionType: NONE, and turning a routine review into a remediation sprint. Turning SSE on ahead of time removes the finding entirely.


How to fix it

You enable encryption on an existing stream with start-stream-encryption. You do not need to recreate the stream, and producers/consumers keep working.

Option 1: AWS-managed key (fastest)

The simplest fix uses the AWS-managed Kinesis key, referenced by the alias alias/aws/kinesis:

aws kinesis start-stream-encryption \
  --stream-name my-event-stream \
  --encryption-type KMS \
  --key-id alias/aws/kinesis

This applies encryption to records written after the call completes. Records already on disk stay as they were written, so let your retention window roll over if you need every record encrypted.

Warning: Encryption only applies to new records. Existing records in the stream are not retroactively encrypted. If your retention is 7 days, all data will be encrypted within 7 days of enabling SSE. For sensitive data already in the stream, treat it as exposed until the retention window clears.

Option 2: Customer-managed KMS key (recommended)

For production and regulated data, use a customer-managed key (CMK). You control the key policy, rotation, and access, and you get CloudTrail logging on every key use.

# Create a key for Kinesis encryption
aws kms create-key \
  --description "Kinesis SSE key" \
  --query 'KeyMetadata.KeyId' --output text

# Give it a friendly alias
aws kms create-alias \
  --alias-name alias/kinesis-prod \
  --target-key-id <key-id>

# Enable encryption on the stream with the CMK
aws kinesis start-stream-encryption \
  --stream-name my-event-stream \
  --encryption-type KMS \
  --key-id alias/kinesis-prod

Make sure the IAM roles for your producers and consumers can use the key. At minimum they need kms:GenerateDataKey (producers) and kms:Decrypt (consumers), plus kms:DescribeKey:

{
  "Effect": "Allow",
  "Action": [
    "kms:GenerateDataKey",
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "arn:aws:kms:us-east-1:111122223333:key/<key-id>"
}

Warning: If your consumers lack kms:Decrypt on the key after you enable a CMK, they will start failing to read records. Update the consumer IAM policies before calling start-stream-encryption to avoid breaking your pipeline.

Option 3: Console

  1. Open the Kinesis console and select Data streams.
  2. Choose your stream, then open the Configuration tab.
  3. Under Server-side encryption, choose Edit.
  4. Enable encryption and select either the AWS-managed key or your CMK.
  5. Save. The change takes effect within a couple of minutes.

Fix it in IaC

Terraform makes encryption a first-class argument on the stream resource:

resource "aws_kinesis_stream" "events" {
  name             = "my-event-stream"
  shard_count      = 2
  retention_period = 48

  encryption_type = "KMS"
  kms_key_id      = aws_kms_key.kinesis.arn
}

resource "aws_kms_key" "kinesis" {
  description             = "Kinesis SSE key"
  enable_key_rotation     = true
  deletion_window_in_days = 14
}

For CloudFormation:

{
  "Type": "AWS::Kinesis::Stream",
  "Properties": {
    "Name": "my-event-stream",
    "ShardCount": 2,
    "StreamEncryption": {
      "EncryptionType": "KMS",
      "KeyId": "alias/kinesis-prod"
    }
  }
}

Tip: Set enable_key_rotation = true on the CMK. AWS will rotate the backing key material annually with no action from you and no change to the key ID, so nothing downstream breaks.


How to prevent it from happening again

A one-time fix does not stop the next stream from being created without encryption. Push the control into the places where streams get created.

Catch it in CI with a Terraform policy

If you use Terraform, an OPA/Conftest policy can fail any plan that defines a stream without KMS encryption:

package kinesis

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_kinesis_stream"
  encryption := resource.change.after.encryption_type
  encryption != "KMS"
  msg := sprintf("Kinesis stream '%s' must use KMS encryption", [resource.change.after.name])
}

Wire that into your pipeline so a pull request introducing an unencrypted stream never merges.

Enforce it organization-wide with an SCP

A Service Control Policy can deny the StopStreamEncryption action so no one can turn encryption off, even with admin-level Kinesis permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyKinesisDisableEncryption",
      "Effect": "Deny",
      "Action": "kinesis:StopStreamEncryption",
      "Resource": "*"
    }
  ]
}

Danger: Test SCPs in a non-production OU before applying them organization-wide. A broad deny that catches an automation role you forgot about can break deploys across every account under the policy.

Detect drift continuously

Lensix runs the kinesis_unencrypted check across your accounts on an ongoing basis, so a stream created outside your IaC, or one where someone disabled SSE, surfaces as a finding rather than waiting for the next audit. Continuous detection closes the gap that point-in-time scans leave open.


Best practices

  • Default to a customer-managed key. The AWS-managed key works, but a CMK gives you key policies, CloudTrail visibility on every decrypt, and the ability to revoke access independently of Kinesis permissions.
  • Enable encryption at creation, not after. Bake encryption_type = "KMS" into your stream modules so no stream is ever born unencrypted.
  • Keep retention as short as your use case allows. A shorter retention window limits how long any record persists on disk, encrypted or not.
  • Audit KMS key access alongside Kinesis access. With a CMK, the key policy is part of your access control surface. Review who can Decrypt as carefully as who can read the stream.
  • Pair encryption with least-privilege IAM. SSE protects data at rest. It does nothing about an over-permissive read policy. Treat the two controls as a pair.

Encryption on a Kinesis stream is a single API call with no impact on producers or consumers. There is almost no reason to leave it off, and several reasons, from audits to access control, to turn it on everywhere. Make it the default and enforce it in your pipeline so the question never comes up again.