Skip to main content
emnode / learn
Compliance

Require TLS for storage and remaining services

One capability across S3, ECS-mounted EFS file systems and load balancer cipher policies: close the leftover in-transit gaps that do not fit the database, API or listener buckets.

12 min·10 sections·AWS

Last reviewed

Remediates AWS Security Hub: ECS.18ELB.17S3.5

The remaining in-transit gaps: the basics

Three different shapes of the same plaintext risk

Most encryption-in-transit work lands neatly on databases, APIs or load balancer listeners. This capability sweeps up the controls that do not fit those buckets but carry the identical risk: data crossing a network in clear text when it should be wrapped in TLS. The three live in very different places. An S3 bucket without an SSL-only policy accepts requests over plain HTTP. An ECS task that mounts an EFS file system without in-transit encryption sends every NFS read and write across the VPC unencrypted. A load balancer listener pinned to an old cipher policy negotiates weak, downgradable TLS even though it technically terminates encryption.

Security Hub expresses each as its own control. S3.5 fails any general-purpose bucket whose policy does not explicitly deny non-SSL requests via the aws:SecureTransport condition. ECS.18 fails a task definition whose efsVolumeConfiguration does not set transitEncryption to ENABLED. ELB.17 fails an ALB or NLB listener whose SslPolicy is not on AWS's recommended list, the only HIGH-severity control in this group because the gap between a 2016 cipher set and a current TLS 1.3 policy is the difference between protected and trivially downgradable. They look unrelated on the report, but they are one capability: refuse plaintext, and where TLS is already on, make sure it is strong.

The fixes are cheap and free in AWS terms, and each is a single configuration change. S3.5 is a bucket-policy statement, ECS.18 is one task-definition field, ELB.17 is a listener SslPolicy swap. The discipline is finding every affected resource (S3.5 in particular fails on absence, so the count tracks bucket-creation velocity) and confirming the change breaks nothing, an old HTTP client, a legacy mount, an ancient TLS client, before you enforce.

In this lesson you will learn how AWS expresses these leftover in-transit gaps across S3, ECS-mounted EFS and load balancer cipher policies, how to apply the canonical S3 SSL-only bucket policy, the one task-definition field that encrypts an EFS mount, and the listener SslPolicy swap that satisfies the cipher check. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The most-failed control is the one that fails on silence

S3.5 sits near the top of every list of most non-compliant Security Hub findings, and not because teams refuse to fix it. The failure mode is silent: a bucket gets created during a Friday afternoon experiment, no policy is attached, and the finding fires in the background. There is no incident, no broken deploy, nothing demanding attention, so the bucket quietly fails the audit for months. The same blind spot runs through this whole group: an EFS mount works perfectly fine in plaintext, and a load balancer on a 2016 cipher policy still speaks TLS 1.2 to a modern browser. Nothing breaks, so nobody looks, which is exactly why these controls exist.

Finding the leftover plaintext gaps across an estate

Marco is the on-call engineer when a Security Hub digest lands showing dozens of S3.5 failures across the production account, plus a handful of ECS.18 and ELB.17 findings. The audit is in six weeks and the security team wants the counts at zero.

Rather than fix one resource at a time, he starts with the largest group. He checks whether a representative bucket already has a policy, because if one exists he has to merge his Deny statement in rather than overwrite it.

Check whether the bucket already has a policy. If it does, the new SSL-only Deny must be merged with what is there, not put on top of it.

$ aws s3api get-bucket-policy --bucket acme-prod-logs --query Policy --output text | jq '.Statement[] | {Sid, Effect, Condition}'
{
"Sid": "AllowCloudFrontReadAccess",
"Effect": "Allow",
"Condition": null
}
# Existing Allow for CloudFront, keep it. No SecureTransport Deny anywhere, S3.5 fails.

A bucket with an existing Allow but no insecure-transport Deny fails S3.5. Fetch first, merge, then put.

How the remaining in-transit controls workdeep dive

S3.5 turns on the global IAM condition key aws:SecureTransport, which AWS sets to true when a request arrived over TLS and false otherwise. The canonical satisfying statement is an explicit Deny gated by Bool aws:SecureTransport false, which is additive: an explicit Deny always wins over any Allow, so it sits alongside whatever Allow statements you already have and only kicks in on a non-TLS request. Security Hub reads the bucket policy and requires the Deny to cover both the bucket and its objects (arn:aws:s3:::bucket and arn:aws:s3:::bucket/*); miss either resource and the control still fails. The same condition key works on SQS, SNS and KMS resource policies, but S3 is the most visible use of it.

ECS.18 evaluates the latest active revision of each AWS::ECS::TaskDefinition and reads the efsVolumeConfiguration block of every EFS volume. When an ECS task mounts EFS it talks over NFS, which is plaintext by default; the control passes only when transitEncryption is set to ENABLED, which wraps that NFS connection in TLS. This is distinct from EFS at-rest encryption, a separate setting on the file system itself, and the two are easy to confuse: a team enables at-rest, sees a green check, and assumes end-to-end protection while the bytes between task and file system still cross the VPC in the clear.

ELB.17 reads the SslPolicy on each ALB and NLB HTTPS or TLS listener and fails it if the policy is not on AWS's recommended list. A policy is an AWS-curated bundle of allowed protocol versions plus a preference-ordered cipher list; the old ELBSecurityPolicy-2016-08 permits TLS 1.0 and 1.1 and ciphers no modern review signs off on, while the current ELBSecurityPolicy-TLS13-* family is TLS 1.3 with a TLS 1.2 fallback and no weak ciphers. It is the only HIGH-severity control here because a listener on a 2016 policy is trivially downgradable even though it terminates TLS, where S3.5 and ECS.18 are Medium.

What is the impact of leaving these gaps open?

The direct impact is data on the wire in clear text or with weak protection. An S3 request over HTTP exposes the entire payload, object names, signed-URL parameters, headers and the object body, and for private buckets the IAM credentials travel in the Authorization header on every call, so observing one HTTP request can compromise the session. An unencrypted EFS mount exposes every file read and write to anyone with VPC network visibility. A listener on a 2016 cipher policy can be forced down to a protocol an attacker on the path can break, so the connection your users trusted is already compromised.

The second-order impact is regulatory. SOC 2, ISO 27001, HIPAA, PCI DSS, FedRAMP and GDPR all require encryption of sensitive data in transit. A bucket failing S3.5 is hard to defend even if you can show the actual traffic was HTTPS, because the policy is what enforces the behaviour and "we trust clients to do the right thing" is not a control. Auditors look at configuration, not traffic logs.

The financial impact is small in dollar terms (no AWS charge differential for any of these) but large in audit-finding terms. Each non-compliant resource is a separate line item; a single account commonly has dozens to hundreds of S3.5 failures alone, and remediation and auditor hours add up even though the bill never moves.

The risk that matters most is silent. There is no error when a client uses HTTP, mounts EFS without TLS, or negotiates an old cipher; the request just succeeds. By the time you discover an old service or a legacy mount has been leaking data, it has often been doing so for months. Closing this well means fixing the flagged resources and changing the default so new ones are born compliant, which converts an open-ended audit line into a one-time cleanup plus a guardrail.

How do you require TLS across these services safely?

Work the capability as one loop. The order matters: inventory and check clients before you enforce, so a deny or a tighter cipher policy does not break a live caller, and finish with prevention so new resources do not rebuild the backlog.

1. Inventory every affected resource and check current clients

List every general-purpose S3 bucket and whether its policy has the aws:SecureTransport Deny, every ECS task definition's efsVolumeConfiguration, and every ALB and NLB listener's SslPolicy. For S3 and listeners, enable access logging or CloudTrail data events for a short window to catch any caller still using HTTP or an old TLS client, recent SDKs default to TLS but old scripts, embedded clients and copy-pasted samples sometimes do not. Catch them now or they break after the change lands.

2. Apply the canonical S3 SSL-only policy without trampling existing statements

Fetch the existing bucket policy, merge the Deny statement gated by Bool aws:SecureTransport false (covering both the bucket and bucket/*), then put it back. Never overwrite: put-bucket-policy replaces the entire policy in one call, so a one-line edit becomes a full wipe if you forget the existing Allow statements. Always fetch, merge, then put.

3. Encrypt the EFS mount and swap the listener policy

Set transitEncryption to ENABLED in the efsVolumeConfiguration of the ECS task definition and register a new revision, then update the service to use it. For listeners, modify the SslPolicy to a current recommended policy such as ELBSecurityPolicy-TLS13-1-2-2021-06. Both changes take effect on the next deployment or immediately on the listener; verify Security Hub clears each finding on its next evaluation.

4. Prevent recurrence with AWS Config and provisioning baselines

Bulk fixes are temporary because new resources are created constantly. Enable the managed Config rules s3-bucket-ssl-requests-only and elb-tls-https-listeners-only, bake the SSL-only policy, the EFS transit-encryption flag and the strong listener policy into your IaC modules, and for new accounts ship the S3 deny as part of the Control Tower baseline. An SCP can require the aws:SecureTransport deny on every PutBucketPolicy. That is what keeps the counts near zero by default.

# 1. The canonical statement Security Hub S3.5 looks for (merge into the existing policy).
cat <<'EOF'
{
  "Sid": "DenyInsecureTransport",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::acme-prod-logs",
    "arn:aws:s3:::acme-prod-logs/*"
  ],
  "Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
EOF
aws s3api put-bucket-policy --bucket acme-prod-logs --policy file://acme-prod-logs-policy.json

# 2. Swap a load balancer listener onto a recommended TLS policy (closes ELB.17).
aws elbv2 modify-listener \
  --listener-arn arn:aws:elasticloadbalancing:eu-west-1:123456789012:listener/app/web-prod/abc/def \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06

Quick quiz

Question 1 of 5

Security Hub shows S3.5, ECS.18 and ELB.17 failures. What is the most efficient way to think about them?

You can now treat the leftover in-transit gaps as one capability rather than three unrelated findings: inventory every bucket, ECS task definition and listener, check current clients, then enforce the SSL-only bucket policy, the EFS transit-encryption flag and a recommended listener policy, and finish with AWS Config and IaC defaults so new resources are born compliant. The Controls this lesson covers section below links every control in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many AWS Security Hub controls. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.