AWS endpoints such as S3 accept requests over both HTTP and HTTPS. How do you make TLS a hard requirement for a bucket rather than a convention, and what exactly does the control you use evaluate?
answer
- available is not the same as required
- a global boolean on every request
- explicit deny beats every allow
- list the bucket ARN and the object ARN
- only proves the last hop was encrypted
basics
~20 sAttach a resource policy statement that denies every action when the global condition key aws:SecureTransport is false. It is a boolean AWS sets per request, true only when the request reached the AWS endpoint over TLS, so the deny turns plain-HTTP access into an error rather than a lapse.
solid answer
~50 sTLS being *available* is not the same as TLS being *required*, and interviewers like the distinction. The mechanism is the global condition key `aws:SecureTransport`, which AWS evaluates on every request and sets to false when the request arrived at the endpoint unencrypted. You add a `Deny` statement with `Principal: "*"`, `Action: "s3:*"` and a `Bool` condition on `aws:SecureTransport` being `"false"` — an explicit deny, so it beats every allow. Two details matter. The `Resource` list must include both the bucket ARN and the `bucket/*` object ARN, or object-level actions slip past. And the key only tells you the last hop into AWS was encrypted — it says nothing about TLS version, nor about anything behind a proxy. S3 exposes `s3:TlsVersion` if you need to require a minimum. The same condition works in an SCP to make it organization-wide.
code
json · 18 lines{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}go deeper
Know that HTTPS being the default in the SDKs is not enforcement, and that aws:SecureTransport is the condition key that makes TLS mandatory. Being able to describe the deny statement in words is enough.
Write the statement correctly: explicit Deny, Principal: "*", the Bool operator against "false", and both the bucket and object ARNs. Explain why an explicit deny is the right shape rather than an allow.
Show you know the control's limits — one hop only, nothing about version or about traffic before a proxy — and how you would widen it beyond a single bucket with an organization-level policy plus a detective check for gaps.
Own the in-transit posture end to end: where TLS terminates, whether internal legs are encrypted and on what evidence, how certificates are issued and renewed without human deadlines, and how you demonstrate coverage to an auditor across services with uneven support.
## Available versus required Every AWS API endpoint supports TLS, and every SDK uses it by default. That is a default, not a guarantee: a hand-rolled client, an old script, or a misconfigured proxy can still speak plain HTTP to an endpoint that accepts it, and nothing in the account will notice. Making encryption in transit a *requirement* means having a control that rejects the unencrypted request, which is a policy question rather than a networking one. ## The condition key AWS evaluates a set of global condition keys on every request, independent of which service it is for. `aws:SecureTransport` is a boolean in that set: true when the request arrived over an encrypted connection, false when it did not. Because it is global, it works in identity policies, resource policies and service control policies alike. The canonical use is a bucket policy statement: ```json { "Sid": "DenyInsecureTransport", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::example-bucket", "arn:aws:s3:::example-bucket/*" ], "Condition": { "Bool": { "aws:SecureTransport": "false" } } } ``` Three properties make this work. It is an explicit `Deny`, and in AWS policy evaluation an explicit deny beats every allow anywhere — identity policy, bucket policy, or a grant from another account. It uses `Principal: "*"`, so it applies to anonymous callers and to principals in other accounts, not just your own. And the condition is written against the string `"false"` with the `Bool` operator, which is how boolean condition values are expressed in policy JSON. ## The mistake that quietly defeats it The most common defect is listing only `arn:aws:s3:::example-bucket` in `Resource`. In S3, bucket-level actions (`s3:ListBucket`) match the bucket ARN and object-level actions (`s3:GetObject`, `s3:PutObject`) match `bucket/*`. A policy with only the bucket ARN denies insecure *listing* while leaving insecure object reads and writes entirely allowed — which is exactly backwards from what you wanted, and it passes a casual review because the statement looks right. Always list both ARNs. ## What the control does and does not prove `aws:SecureTransport` is a statement about one hop: the connection that terminated at the AWS service endpoint. It does not tell you: - **Which TLS version or ciphers** were negotiated. If you need a floor, S3 exposes `s3:TlsVersion` as a numeric condition key you can use in the same policy shape. - **What happened before that hop.** If a client talks HTTP to a proxy that re-originates the request over HTTPS, the condition sees `true` and the first leg was still in the clear. - **Anything about the data at rest.** Transit and rest are independent controls; each has to be enforced on its own. ## Widening it beyond one bucket A per-bucket policy protects one bucket, which is fine for a sensitive dataset and useless as a posture. Two ways to widen it: - **Service control policies.** The same `Bool` condition in an SCP applied to an organizational unit denies non-TLS calls across every account beneath it, including from the accounts' own administrators. That is what turns the rule from a convention into an invariant. - **Detective coverage.** Because not every service exposes the same knobs, pair the preventive rule with a check that reports resources missing the statement, and report coverage per service rather than claiming the whole estate. ## The other half of in-transit encryption Enforcing TLS to AWS API endpoints is only the AWS-side of the story. For your own traffic, TLS is usually terminated at the edge: a certificate issued by AWS Certificate Manager attached to an Application Load Balancer, a CloudFront distribution or an API Gateway endpoint. ACM issues public certificates at no charge for use with those integrated services and renews them automatically as long as the DNS validation records stay in place — which removes the classic outage of a manually renewed certificate expiring at 3am. The leg *behind* the terminator is a separate decision: traffic from the load balancer to the targets is a fresh connection, and whether it is encrypted is a choice you make explicitly rather than something the front-end certificate implies.
- Your policy denies non-TLS access but a colleague says an old client is still writing objects over HTTP. What would you check first?The `Resource` list. Object-level actions match `arn:aws:s3:::bucket/*`, not the bare bucket ARN, so a statement listing only the bucket denies listing while leaving `PutObject` and `GetObject` untouched. Confirm both ARNs are present, then verify with a deliberate plain-HTTP request that it now fails.
- How would you require not just TLS but a minimum TLS version for a bucket?S3 exposes the `s3:TlsVersion` condition key, which carries the negotiated version as a number, so you add a second `Deny` using a numeric less-than comparison against your floor. Keep the `aws:SecureTransport` statement as well — one enforces that TLS happened at all, the other enforces which version, and they fail differently.
- Does an ACM certificate on an Application Load Balancer mean the whole path is encrypted?No. It encrypts the client-to-load-balancer leg only. The connection from the load balancer to its targets is separate: it is HTTP unless the target group's protocol says otherwise. Deciding whether that internal leg needs TLS is a threat-model call about who can observe traffic inside the VPC, not something the public certificate answers.
saying these in an interview costs you the question
- Assumes TLS is guaranteed because the SDK defaults to HTTPS
- Writes the deny with only the bucket ARN, missing object actions
- Uses Effect Allow with SecureTransport true instead of an explicit deny
- Thinks aws:SecureTransport also pins the TLS version
- Believes an edge certificate encrypts the load balancer to target leg