mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
feat(media-proxy): sign attachment URLs and gate origins (#2830)
This commit is contained in:
1 parent
025c01ab13
commit
522cf08e61
108 files changed
+10148
-441
No files matched your search
@@ -14,9 +14,9 @@ An unsuccessful response body is an English reason phrase under the content type
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad request | An invalid storage key, dimension, format, or external path, an unparsable relay `partNumber`, a failed attachment or external transformation, or a relay upload body the endpoint failed to read from the client<sup>1</sup> |
|
||||
| 401 | Unauthorized | An invalid signed external path signature, a missing, malformed, or expired relay capability, or a missing or invalid internal bearer token |
|
||||
| 403 | Media access denied | The media access allowlist rejected the client address |
|
||||
| 403 | Forbidden | A relay capability presented for another bucket, key, method, `uploadId`, or `partNumber`, or `/_metrics` requested from a non-loopback address |
|
||||
| 404 | Not found | A `GET` or `HEAD` of an unrouted path, an object that does not exist, or a relay `PUT` outside `upload` mode |
|
||||
| 403 | Forbidden | A relay capability presented for another bucket, key, method, `uploadId`, or `partNumber`, `/_metrics` requested from a non-loopback address, or a public read whose `Origin` the [cross-origin read policy](/media-proxy/overview/#cross-origin-reads) refuses |
|
||||
| 404 | Not found | A `GET` or `HEAD` of an unrouted path or of any read path on a `relay` endpoint, an object that does not exist, or a relay `PUT` outside `upload` and `relay` mode |
|
||||
| 404 | This content is no longer available.<sup>4</sup> | An attachment read `enforce` refuses, or a missing attachment object under `enforce` |
|
||||
| 405 | Method not allowed<sup>2</sup> | A method other than `GET` or `HEAD` on a read route or on an unrouted path |
|
||||
| 413 | Payload too large | A stored object, external body, upload body, or internal request body beyond its bound |
|
||||
| 500 | Transcode failed | An image asset transcode failed and the source is not directly displayable |
|
||||
@@ -27,13 +27,15 @@ An unsuccessful response body is an English reason phrase under the content type
|
||||
|
||||
<sup>1</sup> The relay also answers 400 when the client connection fails part way through the body
|
||||
|
||||
<sup>2</sup> The registered paths, `/_health`, `/_metrics`, `/_metadata`, `/_thumbnail`, `/_frames`, and `/v1/relay/{key}`, answer an unaccepted method with an empty body, no `Content-Type`, and an `Allow` header
|
||||
<sup>2</sup> The registered paths, `/_health`, `/_metrics`, `/_metadata`, `/_sniff`, `/_thumbnail`, `/_frames`, and `/v1/relay/{key}`, answer an unaccepted method with an empty body, no `Content-Type`, and an `Allow` header. All seven are registered in every mode, so the method rejection is the same in every mode, even where the path itself answers 404
|
||||
|
||||
<sup>3</sup> `/_metadata` is the only endpoint that remaps an origin 429. The signed external read route retains 429 as 429
|
||||
|
||||
<sup>4</sup> Sent under the `enforce` [signed attachment URL policy](/media-proxy/overview/#signed-attachment-urls) alone, where it also replaces `Not found` for an attachment object that does not exist. Under `report` every response is the one `off` returns, so a missing object keeps the `Not found` body there
|
||||
|
||||
When the Media Proxy passes an external origin status through to the client, the body is `Upstream fetch failed` on the signed external read route. On `/_metadata`, the body is the canonical reason phrase of that status. An object store error that maps to no case above uses the canonical reason phrase of its status.
|
||||
|
||||
Every error a route produces uses `Cache-Control: no-store` and the standard [security headers](/media-proxy/overview/#representation-headers). A 416 response, a media access allowlist rejection, and the empty-body method rejection of a registered path have the security headers and set no cache policy, and [Cache policies](#cache-policies) names them. No plain-text error has CORS headers unless it came from the [upload relay](/media-proxy/upload-relay/), and no error has `Retry-After` or a request identifier.
|
||||
Every error a route produces uses `Cache-Control: no-store` and the standard [security headers](/media-proxy/overview/#representation-headers). The exceptions have the security headers and set no cache policy, and [Cache policies](#cache-policies) names them. No plain-text error has CORS headers unless it came from the [upload relay](/media-proxy/upload-relay/), and no error has `Retry-After` or a request identifier. Under the `enforce` [cross-origin read policy](/media-proxy/overview/#cross-origin-reads), every `GET` or `HEAD` error on a read path has `Vary: Accept-Encoding, Origin`. That includes the 403 for a refused `Origin`.
|
||||
|
||||
### Handling contract
|
||||
|
||||
@@ -49,8 +51,8 @@ Every `HEAD` response has an empty body, so the Body column describes `GET`, `PU
|
||||
| 206 | Selected media bytes for `GET` and empty for `HEAD` | One byte range is satisfiable |
|
||||
| 400 | [Media error response](#media-error-response) | A path, query, target, transformation, or relay request is invalid |
|
||||
| 401 | [Media error response](#media-error-response) | An external path signature, a relay capability, or an internal token is invalid |
|
||||
| 403 | [Media error response](#media-error-response) | The media access policy denied the request, a relay capability does not match the request, or an external origin returned 403 |
|
||||
| 404 | [Media error response](#media-error-response) | The route or object does not exist, the relay is not served here, or an external origin returned 404 |
|
||||
| 403 | [Media error response](#media-error-response) | The cross-origin read policy denied the request, a relay capability does not match the request, or an external origin returned 403 |
|
||||
| 404 | [Media error response](#media-error-response) | The route or object does not exist, a `relay` endpoint received a read, the relay is not served here, an attachment signature was refused, or an external origin returned 404 |
|
||||
| 405<sup>2</sup> | [Media error response](#media-error-response) | The route rejects the method, or an external origin returned 405 |
|
||||
| 406, 408, 409, 410, 411, 412, 414, 415, 428, 429<sup>3</sup> | [Media error response](#media-error-response) | An external origin returned that status |
|
||||
| 413 | [Media error response](#media-error-response) | A source, external response, or upload exceeds its applicable bound |
|
||||
@@ -60,7 +62,7 @@ Every `HEAD` response has an empty body, so the Body column describes `GET`, `PU
|
||||
| 503 | [Media error response](#media-error-response) | Upload spool capacity is exhausted, an external buffer reservation or allocation failed, or an external origin answered `/_metadata` with 429<sup>5</sup> |
|
||||
| 504 | [Media error response](#media-error-response) | Transformation capacity was unavailable or a transformation deadline expired |
|
||||
|
||||
<sup>1</sup> The [internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) `/_metadata` and `/_frames` answer 200 with JSON, `/_health` with plain text, and `/_metrics` with the Prometheus text exposition
|
||||
<sup>1</sup> The [internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) `/_metadata`, `/_sniff`, and `/_frames` answer 200 with JSON, `/_health` with plain text, and `/_metrics` with the Prometheus text exposition
|
||||
|
||||
<sup>2</sup> A read route path, or any other path outside the registered paths, answers with the [media error response](#media-error-response). Each registered path answers with an empty body, no `Content-Type`, and an `Allow` header
|
||||
|
||||
@@ -88,7 +90,7 @@ Decoded images are limited to 16,384 pixels on either edge and 268,435,456 pixel
|
||||
|
||||
The upload relay limits a body to the smaller of the authorised upload size and the endpoint body limit. That endpoint limit defaults to 500 MiB and can be configured from 1 byte through 5 GiB.
|
||||
|
||||
An internal `/_metadata`, `/_thumbnail`, or `/_frames` request body is limited to the base64 expansion of the 500 MiB media bound plus 1 MiB. All answer a larger body with 413.
|
||||
An internal `/_metadata`, `/_sniff`, `/_thumbnail`, or `/_frames` request body is limited to the base64 expansion of the 500 MiB media bound plus 1 MiB. All answer a larger body with 413.
|
||||
|
||||
## Work admission
|
||||
|
||||
@@ -118,13 +120,17 @@ A status and its headers are chosen before the body is sent. A streamed object s
|
||||
|
||||
Every successful media representation uses `Cache-Control: public, max-age=31536000` and `CDN-Cache-Control: public, max-age=31536000`. An audio or video representation appends `no-transform` to `Cache-Control` only. Signed external media uses the same policy as stored media.
|
||||
|
||||
No route sets `immutable`, `Expires`, `ETag`, or `Last-Modified` on a read response, and no route selects a shorter cache policy. A cache revalidates a representation by fetching it again.
|
||||
No route sets `immutable`, `Expires`, `ETag`, or `Last-Modified` on a read response. A cache revalidates a representation by fetching it again.
|
||||
|
||||
Every route-produced error response uses `Cache-Control: no-store`, and a successful `/_metrics` read uses it too. A 416 response, a successful upload relay response, a media access allowlist rejection, and the empty-body method rejection of a registered path set no cache policy at all.
|
||||
One read selects a shorter policy. Under the `report` or `enforce` [signed attachment URL policy](/media-proxy/overview/#signed-attachment-urls), an attachment read whose signature is valid gets `public, max-age=` the seconds left on that signature in both headers, so no entry outlives the URL that made it. A data package URL never expires, so it gets the one-day ceiling instead. Every other read, and every attachment read under `off`, keeps the year.
|
||||
|
||||
A one-year `max-age` is only safe where a purge path reaches every cache that stores media. With no `ETag` and no `Last-Modified` there is nothing to revalidate against, so a deleted or replaced object is served until its entry is purged or drops out. A cache with no purge path must set a shorter lifetime of its own instead. [Caching in front of Fluxer](/operator/reverse-proxy/#caching-in-front-of-fluxer) states how.
|
||||
|
||||
Every route-produced error response uses `Cache-Control: no-store`, and a successful `/_metrics` read uses it too. A 416 response, a successful upload relay response, and the empty-body method rejection of a registered path set no cache policy at all.
|
||||
|
||||
## Range response headers
|
||||
|
||||
A complete media response has `Accept-Ranges: bytes`, the representation `Content-Type`, and an exact `Content-Length`<sup>1</sup>. A 206 also sets `Content-Range` to the selected interval over the complete size and `Content-Length` to the selected byte count. A 416 has `Content-Range: bytes */{size}` and `Accept-Ranges: bytes` with an empty body.
|
||||
A complete media response has `Accept-Ranges: bytes`, the representation `Content-Type`, and an exact `Content-Length`<sup>1</sup>. Outside `static` mode that `Content-Type` is `text/plain; charset=utf-8` when it names a JavaScript media type, and a 200 or 206 with no `Content-Type` gets the same value. [Content detection](/media-proxy/overview/#content-detection) defines both rules. A 206 also sets `Content-Range` to the selected interval over the complete size and `Content-Length` to the selected byte count. A 416 has `Content-Range: bytes */{size}` and `Accept-Ranges: bytes` with an empty body.
|
||||
|
||||
<sup>1</sup> A streamed signed external response omits `Content-Length` when the origin declared none
|
||||
|
||||
|
||||
Reference in new issue
Block a user