Requiring encryption on the wire

Lesson 1 of 3 in Protecting Data in Transit.

There is a difference between offering encryption and requiring it, and the entire exam reflex for this topic lives in that gap. Enabling an HTTPS listener means clients may connect securely. Adding a redirect means most clients will. Neither prevents a misconfigured script, an old SDK, or an attacker from opening the plaintext path that is still listening — and “ensure data cannot be transmitted unencrypted” is a requirement that only a deny satisfies.

The deny for AWS API-addressable resources is a resource policy condition on the global key aws:SecureTransport, which is false when the request did not use TLS. An explicit Deny on aws:SecureTransport: false in a bucket policy, an SQS queue policy, an SNS topic policy, a Secrets Manager resource policy or a KMS key policy closes the plaintext door no matter what the identity policies allow — explicit deny wins over every allow in AWS policy evaluation. S3 adds a second condition key, s3:TlsVersion, which carries the negotiated TLS version as a number so you can refuse connections below your floor: NumericLessThan: {"s3:TlsVersion": 1.2}.

For traffic that terminates on a load balancer rather than a service API, the control is the ELB security policy on the listener. It is a named bundle of protocol versions and cipher suites; pick one whose minimum version matches your policy (the TLS 1.2-and-above and TLS 1.3 families), and the load balancer will refuse to negotiate anything weaker. The listener-level analogue of the bucket policy deny is simply not having an HTTP listener at all — or having one whose only action is a 301 redirect to HTTPS, which keeps old links working without carrying any payload in the clear. Finish the job at the application layer with HSTS: a Strict-Transport-Security response header, injectable on CloudFront with a response headers policy, tells browsers to refuse plaintext for your domain for a stated period, which removes the first unencrypted request that a redirect cannot.

Deny unencrypted S3 access

The canonical answer to “clients must not be able to read or write this bucket without TLS”. Note Principal: "*" plus Deny — a deny statement should be as broad as possible.

{
  "Sid": "DenyUnencryptedTransport",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::reports-prod",
    "arn:aws:s3:::reports-prod/*"
  ],
  "Condition": { "Bool": { "aws:SecureTransport": "false" } }
}

Both ARNs matter: the bucket for bucket-level actions, the /* form for object actions. A policy with only one of them leaves half the API uncovered.

Require a minimum TLS version

s3:TlsVersion is numeric, so the floor is expressed with a numeric operator.

{
  "Sid": "DenyOldTlsVersions",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::reports-prod",
    "arn:aws:s3:::reports-prod/*"
  ],
  "Condition": { "NumericLessThan": { "s3:TlsVersion": 1.2 } }
}

Deploy this one with care: it breaks old SDKs and embedded clients immediately and silently from their point of view. Find them first in CloudTrail — the TLS version of each request is recorded there, which makes a dry-run audit possible before enforcement.

Deny unencrypted queue and topic traffic

The same condition key works wherever a resource policy does, which is what makes it a pattern rather than an S3 trick.

{
  "Sid": "DenyPlaintextSqs",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "sqs:*",
  "Resource": "arn:aws:sqs:eu-west-1:111122223333:orders",
  "Condition": { "Bool": { "aws:SecureTransport": "false" } }
}

At organization scale the same statement belongs in a resource control policy (RCP) so that no account can create a resource without it — the governance module covers that mechanism.

HSTS at the edge

A CloudFront response headers policy adds the header to every response without touching application code.

{
  "SecurityHeadersConfig": {
    "StrictTransportSecurity": {
      "AccessControlMaxAgeSec": 31536000,
      "IncludeSubdomains": true,
      "Preload": true,
      "Override": true
    }
  }
}

HSTS protects the next request, not the first one, and includeSubdomains with a long max-age is effectively irreversible for the duration — so make sure every subdomain can serve HTTPS before you set it.

Which segment should terminate TLS is a design decision with three defensible answers, and the exam tests whether you can tell which requirement forces which.

Terminate at the edge or the load balancer. CloudFront or an ALB holds the certificate, decrypts, inspects, and forwards to targets. This is the default for good reasons: one certificate to manage and renew, HTTP-aware features like AWS WAF rule evaluation, header inspection, sticky sessions and path routing all need plaintext, and the compute behind it never deals with TLS. The cost is that traffic from the load balancer to the target is unencrypted unless you choose otherwise.

Re-encrypt to the target. The load balancer decrypts, inspects, then opens its own TLS connection to the target — an HTTPS target group on an ALB, or an HTTPS/TLS protocol on an NLB target. The targets need certificates, but they can be private certificates from AWS Private CA because the load balancer does not validate the target’s certificate chain the way a browser would. This is the answer whenever a requirement says “encrypted end to end”, “no plaintext inside the VPC”, or names a compliance regime like PCI DSS or HIPAA.

Pass through without decrypting. An NLB TCP listener or a CLB in TCP mode forwards the encrypted bytes untouched, so TLS terminates on the instance itself and the load balancer never holds a key or sees a payload. Choose it when the application must perform mutual TLS client-certificate authentication itself, or when the workload is not HTTP at all. Note that an ALB can also do mTLS itself — verifying client certificates against a trust store — which is often the better answer when the requirement is “authenticate clients with certificates” rather than “the application must see the certificate”. The price of pass-through is that no AWS layer-7 control can inspect what it cannot read.

TLS termination patterns and the requirement each one answers
PatternWho holds the certificateEncrypted inside the VPCLayer-7 inspection possiblePick it when

Terminate at CloudFront / ALB

ACM certificate on the distribution or listener

No — load balancer to target is plaintext

Yes — WAF, headers, routing, caching

The default: public web traffic with edge controls and no in-VPC encryption mandate

Re-encrypt to the target

Public certificate at the edge, private certificate on each target

Yes — every hop encrypted

Yes — decryption still happens at the load balancer

A written requirement for end-to-end encryption, or a regulated workload

TCP pass-through (NLB)

The instance or container — the load balancer never sees a key

Yes — the load balancer cannot decrypt it

No — the bytes are opaque to AWS

The application must terminate TLS itself, e.g. to inspect client certificates, or the protocol is not HTTP

mTLS at the ALB

ALB holds the server certificate and a trust store for client CAs

Only if the target group is HTTPS too

Yes

Clients must authenticate with certificates, but the application does not need to see them

Key terms: aws:SecureTransport, s3:TlsVersion, ELB security policy, TLS termination, HTTP Strict Transport Security (HSTS)

Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.