Public access, CORS and forms¶
Everything in leBLOB is private unless made public. This page covers the three features that open a store to people without a key: public buckets and objects, CORS rules for pages in a browser, and form uploads.
Public buckets and objects¶
leBLOB supports the two canned ACLs private and public-read, and nothing finer. A request with no signature at all is anonymous, and may read what one of them made public. A request with a wrong signature isn't anonymous: it is refused, so a client with a bad key gets an error.
The two kinds are separate, as in S3:
- A public bucket can be listed by anyone. The objects in it stay private unless they are public too.
- A public object can be read by anyone, with GET or HEAD, even in a private bucket.
Set an ACL when creating a bucket or writing an object, or afterwards:
aws --endpoint-url https://s3.example.org s3api create-bucket --bucket site --acl public-read
aws --endpoint-url https://s3.example.org s3 cp index.html s3://site/ --acl public-read
aws --endpoint-url https://s3.example.org s3api put-object-acl --bucket site --key old.html --acl private
x-amz-acl works on CreateBucket, PutObject, CopyObject and CreateMultipartUpload, and PUT ?acl accepts either the header or an AccessControlPolicy body that says no more than one of the canned ACLs. Any other grant gets 501 NotImplemented.
Each version of an object has its own ACL. Overwriting an object makes it private again unless the new write asks for public-read, and a copy doesn't inherit the source's ACL.
What a stranger can and can't do¶
An anonymous request can list a public bucket, and read or check a public object. It can't write, change ACLs or tags, or list buckets or uploads. It can't use the response-* parameters that change the headers of an answer either.
A missing bucket and a private one answer the same 403 AccessDenied, and so does a missing object, unless its bucket is public, whose listing would show it anyway.
Who may publish¶
On a scoped key, making anything public requires the admin level, on a write with x-amz-acl: public-read as much as by PUT ?acl. Making something private needs write. A stolen write key can't expose what the store keeps.
There are no bucket policies, no "block public access" setting, and no switch to forbid publishing on a whole store.
CORS¶
A page in a browser can only read answers from another origin if the server says so. A bucket's CORS rules, set with PutBucketCors, say which origins may send which requests. On the wire, the rules are XML:
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>https://app.example.org</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
<MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
The AWS CLI takes the same rules as JSON:
{
"CORSRules": [
{
"AllowedOrigins": ["https://app.example.org"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
}
aws --endpoint-url https://s3.example.org s3api put-bucket-cors --bucket my-bucket \
--cors-configuration file://cors.json
The methods allowed are GET, PUT, POST, DELETE and HEAD. A bucket holds at most 100 rules. An origin or a header can contain one * as a wildcard.
A CORS rule grants no access. It only lets a page read the answers. Every request still needs a signature or a public ACL, so a page needs a key or a presigned URL, as it would without CORS.
Some details:
- The browser's preflight
OPTIONSis the one request answered without a signature. An unknown bucket and a bucket with no rules give the same403, so preflights don't reveal which buckets exist. A bucket with rules does show what they allow, since a browser couldn't work otherwise. - Error answers include the CORS headers too, so a page can see why a request failed.
Access-Control-Allow-Credentialsis never sent: requests are signed, and cookies mean nothing here.- When a bucket has rules and none matches, the answer says
Vary: Origin, so a cache doesn't give one origin's answer to another. - Setting or removing rules needs
adminon a scoped key, and reading themread.
Form uploads¶
A form upload lets a browser send a file straight to a bucket with an HTML form, signed in advance by someone holding a key. It is S3's POST policy, as boto3's generate_presigned_post makes it:
post = s3.generate_presigned_post(
"uploads",
"incoming/${filename}",
Conditions=[["content-length-range", 1, 10 * 1024 * 1024]],
ExpiresIn=3600,
)
# post["url"] and post["fields"] go into the form; the file comes last.
The client has to be created with signature_version="s3v4": forms signed with SigV2 are refused.
The signature covers only the policy, so the policy has to name every field the form sends. A field that no condition names is refused, or a form could add what its signer never saw, such as acl=public-read. The signature, the policy, the file, x-ignore-* fields and checksums are the exceptions. The bucket in the form has to be the bucket in the URL, and content-length-range is enforced.
The key that signed the policy needs write on the bucket, and admin if the form makes the object public. There are no anonymous forms, since nothing in leBLOB allows public writes.
A form cut off before its closing boundary stores nothing. The file is read as it arrives and isn't held in memory. Up to 20 KiB of fields may come before the file, as on AWS.
A form upload can't be encrypted with SSE-C: a form that asks for it is refused.