Follow-up from #1402. Uploads now work in the Docker Compose stack, but files uploaded as Public (for example the admin avatar) still don't render there. BuildPublicUrl returns PublicBaseUrl, or else ServiceUrl (http://rustfs:9000/fsh/...). Browsers can't resolve that host, and the compose bucket has no anonymous-read policy anyway.
Why this isn't a one-line fix
Visibility is not encoded in the storage key. StorageKeyBuilder puts every file under the same layout, so there's no way to grant anonymous read to public files only:
| Stack |
Bucket policy today |
Effect |
Aspire (AppHost.cs) |
anonymous s3:GetObject on fsh/* |
Public files render. Private files are also readable without auth by anyone who has the key. Keys contain a UUIDv7, so they're hard to guess, but "private" isn't enforced by the store. |
| Compose |
none |
Private files are safe, public URLs are broken |
| AWS Terraform |
off by default; app_s3_public_read_prefix (uploads/) if enabled |
Can only work if public files live under a known prefix, which they don't |
Proposal
- Put visibility in the key. Have
StorageKeyBuilder add a public/ or private/ root prefix (existing keys stay valid; only new uploads get it).
- Grant anonymous read only on
public/* in all three stacks: the Aspire bootstrap policy, a new compose bootstrap policy, and the Terraform app_s3_public_read_prefix default changed to public/.
- Compose: set
Storage__S3__PublicBaseUrl=${FSH_S3_PUBLIC_URL}/fsh.
- Changing visibility (if a file can go from public to private) becomes a copy plus delete. We need to check whether the Files module allows that today.
Alternative considered: an API endpoint that answers 302 with a presigned GET for public files. It needs no bucket policy anywhere, but it adds an API round trip per image and URLs change on every request, so browsers and CDNs can't cache them. Prefix + policy is better for public assets, since those are what you want cached.
Also worth doing
Touches src/BuildingBlocks/Storage (key builder, if it lives there) and the Files module, so it needs maintainer sign-off on the design before a PR.
Follow-up from #1402. Uploads now work in the Docker Compose stack, but files uploaded as
Public(for example the admin avatar) still don't render there.BuildPublicUrlreturnsPublicBaseUrl, or elseServiceUrl(http://rustfs:9000/fsh/...). Browsers can't resolve that host, and the compose bucket has no anonymous-read policy anyway.Why this isn't a one-line fix
Visibility is not encoded in the storage key.
StorageKeyBuilderputs every file under the same layout, so there's no way to grant anonymous read to public files only:AppHost.cs)s3:GetObjectonfsh/*app_s3_public_read_prefix(uploads/) if enabledProposal
StorageKeyBuilderadd apublic/orprivate/root prefix (existing keys stay valid; only new uploads get it).public/*in all three stacks: the Aspire bootstrap policy, a new compose bootstrap policy, and the Terraformapp_s3_public_read_prefixdefault changed topublic/.Storage__S3__PublicBaseUrl=${FSH_S3_PUBLIC_URL}/fsh.Alternative considered: an API endpoint that answers
302with a presigned GET for public files. It needs no bucket policy anywhere, but it adds an API round trip per image and URLs change on every request, so browsers and CDNs can't cache them. Prefix + policy is better for public assets, since those are what you want cached.Also worth doing
127.0.0.1:${FSH_S3_PORT}:9000) when the proxy runs on the same host.Touches
src/BuildingBlocks/Storage(key builder, if it lives there) and the Files module, so it needs maintainer sign-off on the design before a PR.