This check flags Kinesis Data Firehose delivery streams running without server-side encryption, which leaves your streaming data readable to anyone who can access the underlying buffers or destinations. Turn on SSE with a KMS key to fix it in one update call.
Kinesis Data Firehose is the workhorse that quietly moves your logs, clickstreams, IoT telemetry, and application events into S3, Redshift, OpenSearch, or a third party like Splunk. Because it sits in the middle of so many data pipelines, it often handles sensitive records without anyone thinking too hard about it. That is exactly why an unencrypted delivery stream is worth catching early.
The kinesis_firehose_unencrypted check looks at each Firehose delivery stream in your AWS account and reports any stream that does not have server-side encryption (SSE) enabled.
What this check detects
Firehose supports server-side encryption for data while it is buffered inside the service, before it gets written to the destination. When SSE is off, records sit in Firehose's internal buffers in plaintext, and the encryption posture of your data depends entirely on whatever the destination happens to do.
This check inspects the delivery stream's encryption configuration and fails when:
- The stream has no
DeliveryStreamEncryptionConfigurationset, or - The encryption status is
DISABLEDrather thanENABLED.
Note: Firehose SSE only applies to direct PUT delivery streams, where producers send records straight to Firehose via the API. If your delivery stream's source is a Kinesis Data Stream, encryption is configured on that data stream instead, and Firehose inherits records already protected upstream.
Why it matters
"Encryption at rest" sounds like a checkbox you tick for compliance, but the practical risk here is concrete. Consider what typically flows through Firehose:
- Application and access logs that contain IP addresses, session tokens, or user IDs
- Payment or transaction events headed for a data warehouse
- IoT device telemetry tied to physical locations or individuals
- CloudWatch Logs subscriptions carrying whatever your apps decide to print
Without SSE, that data is buffered in plaintext, and the key controls you would normally rely on are missing. Three things go wrong:
- No KMS audit trail. With a customer managed KMS key, every decrypt operation shows up in CloudTrail. Without SSE, there is no key usage to log, so you lose a layer of visibility into who touched the data path.
- No key-level access boundary. KMS key policies let you separate "can configure the stream" from "can read the data." Unencrypted streams collapse that distinction.
- Compliance gaps. PCI DSS, HIPAA, SOC 2, and most internal data classification policies expect encryption at rest across the full pipeline, not just the final bucket.
Warning: A common false sense of security is "my S3 destination is encrypted, so I'm fine." That covers the data after it lands. It does nothing for the records buffered inside Firehose, and it ignores the audit and access-control benefits of encrypting the stream itself.
How to fix it
You can enable encryption on an existing delivery stream without recreating it. You have two key options:
- AWS owned key (
AWS_OWNED_CMK): zero setup, no extra cost, but no CloudTrail visibility into key usage and no key policy control. - Customer managed key (
CUSTOMER_MANAGED_CMK): you own the key policy, rotation, and audit trail. This is the recommended choice for anything sensitive.
Option 1: Console
- Open the Kinesis console and select Delivery streams.
- Choose the affected stream and open the Configuration tab.
- Under Server-side encryption, choose Edit.
- Enable encryption and pick either an AWS owned key or one of your KMS keys.
- Save. The status will move to
ENABLINGand thenENABLED.
Option 2: AWS CLI
Enable SSE with an AWS owned key:
aws firehose start-delivery-stream-encryption \
--delivery-stream-name my-stream \
--delivery-stream-encryption-configuration-input KeyType=AWS_OWNED_CMK
Or with a customer managed KMS key (recommended):
aws firehose start-delivery-stream-encryption \
--delivery-stream-name my-stream \
--delivery-stream-encryption-configuration-input \
KeyType=CUSTOMER_MANAGED_CMK,KeyARN=arn:aws:kms:us-east-1:111122223333:key/abcd1234-...
Confirm the change took effect:
aws firehose describe-delivery-stream \
--delivery-stream-name my-stream \
--query 'DeliveryStreamDescription.DeliveryStreamEncryptionConfiguration'
Warning: Encryption only takes effect for records that arrive after the status becomes ENABLED. Records already buffered when you flip the switch are not retroactively encrypted. There is also a brief window where the status is ENABLING, so check the state before assuming the change is live.
Option 3: Terraform
For a direct PUT stream, add the server_side_encryption block:
resource "aws_kinesis_firehose_delivery_stream" "logs" {
name = "my-stream"
destination = "extended_s3"
server_side_encryption {
enabled = true
key_type = "CUSTOMER_MANAGED_CMK"
key_arn = aws_kms_key.firehose.arn
}
extended_s3_configuration {
role_arn = aws_iam_role.firehose.arn
bucket_arn = aws_s3_bucket.destination.arn
}
}
Tip: If you use a customer managed key, the Firehose service role needs kms:GenerateDataKey and kms:Decrypt on that key. Grant it in the KMS key policy or a role policy, otherwise records will fail to deliver and you'll see KMS.DisabledException style errors.
How to prevent it from happening again
Fixing one stream is easy. Keeping every future stream encrypted is the real goal. Build the guardrail into the places teams create infrastructure.
Catch it in CI with policy-as-code
If you use Terraform, a Checkov or OPA/Conftest policy in your pipeline blocks merges that introduce an unencrypted stream. Here is the relevant Checkov check:
# Fails the build if any Firehose stream lacks SSE
checkov -d . --check CKV_AWS_241
A simple Rego rule for Conftest looks like this:
package main
deny[msg] {
resource := input.resource.aws_kinesis_firehose_delivery_stream[name]
not resource.server_side_encryption[_].enabled
msg := sprintf("Firehose stream '%s' must enable server-side encryption", [name])
}
Enforce it at the account level with SCPs
A Service Control Policy can deny stream creation unless SSE is requested, stopping the problem before it reaches an account at all.
Danger: Test SCPs in a non-production OU first. A poorly scoped deny can block legitimate stream creation across every account in the organization, and the failure shows up as a confusing AccessDenied with no obvious cause. Roll it out gradually.
Detect drift continuously
Policy-as-code covers what goes through your pipeline. It does not cover streams created by hand in the console or by another team. Continuous scanning closes that gap. Lensix runs the kinesis_firehose_unencrypted check across your accounts on every scan, so a stream created outside your IaC workflow still gets flagged rather than sitting unnoticed for months.
Best practices
- Default to customer managed keys for any stream carrying regulated or sensitive data. The CloudTrail audit trail and key policy control are worth the small management overhead.
- Enable key rotation on the KMS key so the encryption material refreshes automatically without you touching the stream.
- Encrypt the source too. If Firehose pulls from a Kinesis Data Stream, make sure that data stream has SSE enabled. Encrypting Firehose alone leaves the upstream stream exposed.
- Encrypt the destination as well. SSE on Firehose protects the buffer. The S3 bucket, Redshift cluster, or OpenSearch domain at the end of the pipeline needs its own encryption at rest.
- Tag streams with a data classification so reviewers can quickly tell which streams demand the strictest controls.
- Scope KMS key policies tightly. Only the Firehose service role and the producers that need it should be able to use the key.
Encryption on Firehose is cheap, fast to enable, and removes a real gap in an otherwise easy-to-overlook part of your data pipeline. Turn it on everywhere, then let automated checks keep it that way.

