Configuration options for media redirect
This commit is contained in:
@@ -2182,6 +2182,39 @@ media_upload_limits:
|
||||
max_size: 500M
|
||||
```
|
||||
---
|
||||
### `media_redirect`
|
||||
|
||||
*(object)* When enabled, Synapse will use HTTP redirect responses to serve media instead of directly serving the media from the media store. This can help with caching, but requires /_synapse/media/* to be routed to the media worker.
|
||||
|
||||
This setting has the following sub-options:
|
||||
|
||||
* `enabled` (boolean): Enables the media redirect feature. If enabled, you must specify a `media_redirect.secret` or `media_redirect.secret_path`. Defaults to `false`.
|
||||
|
||||
* `secret` (string): Secret used to sign media redirect URLs. This must be set if `media_redirect.enabled` is set. Defaults to `null`.
|
||||
|
||||
* `secret_path` (string): An alternative to `media_redirect.secret` that specifies a file containing the secret. Defaults to `null`.
|
||||
|
||||
* `ttl` (duration): How long the redirect URLs should be valid for. Defaults to `"10m"`.
|
||||
|
||||
Default configuration:
|
||||
```yaml
|
||||
media_redirect:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Example configurations:
|
||||
```yaml
|
||||
media_redirect:
|
||||
enabled: true
|
||||
secret: aiCh9gu4Zahvueveisooquu7chaiw9Ee
|
||||
```
|
||||
|
||||
```yaml
|
||||
media_redirect:
|
||||
enabled: true
|
||||
secret_path: /path/to/secrets/file
|
||||
```
|
||||
---
|
||||
### `max_image_pixels`
|
||||
|
||||
*(byte size)* Maximum number of pixels that will be thumbnailed. Defaults to `"32M"`.
|
||||
|
||||
@@ -2433,6 +2433,44 @@ properties:
|
||||
max_size: 100M
|
||||
- time_period: 1w
|
||||
max_size: 500M
|
||||
media_redirect:
|
||||
type: object
|
||||
description: >-
|
||||
When enabled, Synapse will use HTTP redirect responses to serve media
|
||||
instead of directly serving the media from the media store. This can help
|
||||
with caching, but requires /_synapse/media/* to be routed to the media
|
||||
worker.
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: >-
|
||||
Enables the media redirect feature. If enabled, you must specify a
|
||||
`media_redirect.secret` or `media_redirect.secret_path`.
|
||||
default: false
|
||||
secret:
|
||||
type: string
|
||||
description: >-
|
||||
Secret used to sign media redirect URLs. This must be set if
|
||||
`media_redirect.enabled` is set.
|
||||
default: null
|
||||
secret_path:
|
||||
type: string
|
||||
description: >-
|
||||
An alternative to `media_redirect.secret` that specifies a file
|
||||
containing the secret.
|
||||
default: null
|
||||
ttl:
|
||||
$ref: "#/$defs/duration"
|
||||
description: >-
|
||||
How long the redirect URLs should be valid for.
|
||||
default: 10m
|
||||
default:
|
||||
enabled: false
|
||||
examples:
|
||||
- enabled: true
|
||||
secret: aiCh9gu4Zahvueveisooquu7chaiw9Ee
|
||||
- enabled: true
|
||||
secret_path: /path/to/secrets/file
|
||||
max_image_pixels:
|
||||
$ref: "#/$defs/bytes"
|
||||
description: Maximum number of pixels that will be thumbnailed.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import attr
|
||||
|
||||
@@ -30,7 +30,7 @@ from synapse.types import JsonDict
|
||||
from synapse.util.check_dependencies import check_requirements
|
||||
from synapse.util.module_loader import load_module
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
from ._base import Config, ConfigError, read_file
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -130,7 +130,9 @@ class MediaUploadLimit:
|
||||
class ContentRepositoryConfig(Config):
|
||||
section = "media"
|
||||
|
||||
def read_config(self, config: JsonDict, **kwargs: Any) -> None:
|
||||
def read_config(
|
||||
self, config: JsonDict, allow_secrets_in_config: bool, **kwargs: Any
|
||||
) -> None:
|
||||
# Only enable the media repo if either the media repo is enabled or the
|
||||
# current worker app is the media repo.
|
||||
if (
|
||||
@@ -290,6 +292,55 @@ class ContentRepositoryConfig(Config):
|
||||
|
||||
self.enable_authenticated_media = config.get("enable_authenticated_media", True)
|
||||
|
||||
redirect_config = config.get("media_redirect", {})
|
||||
if redirect_config is None:
|
||||
redirect_config = {}
|
||||
if not isinstance(redirect_config, dict):
|
||||
raise ConfigError(
|
||||
"`media_redirect` must be a dictionary",
|
||||
("media_redirect",),
|
||||
)
|
||||
|
||||
# Whether we should use a redirect to /_synapse/media/* when serving
|
||||
# media for better caching. This requires this endpoint to be routed to
|
||||
# the media worker.
|
||||
self.use_redirect = redirect_config.get("enabled", False)
|
||||
|
||||
redirect_secret = redirect_config.get("secret")
|
||||
if redirect_secret and not allow_secrets_in_config:
|
||||
raise ConfigError(
|
||||
"Config options that expect an in-line secret as value are disabled",
|
||||
("media_redirect", "secret"),
|
||||
)
|
||||
if redirect_secret is not None and not isinstance(redirect_secret, str):
|
||||
raise ConfigError(
|
||||
"`media_redirect.secret` must be a string.",
|
||||
("media_redirect", "secret"),
|
||||
)
|
||||
|
||||
redirect_secret_path = redirect_config.get("secret_path")
|
||||
if redirect_secret_path:
|
||||
if redirect_secret:
|
||||
raise ConfigError(
|
||||
"You have configured both `media_redirect.secret` and `media_redirect.secret_path`.\n"
|
||||
"These are mutually incompatible.",
|
||||
("media_redirect", "secret_path"),
|
||||
)
|
||||
redirect_secret = read_file(
|
||||
redirect_secret_path, ("media_redirect", "secret_path")
|
||||
).strip()
|
||||
|
||||
self.redirect_secret: Optional[bytes] = (
|
||||
redirect_secret.encode("utf-8") if redirect_secret else None
|
||||
)
|
||||
|
||||
if self.use_redirect and self.redirect_secret is None:
|
||||
raise ConfigError(
|
||||
"You have configured `media_redirect.enabled` but not set `media_redirect.secret` or `media_redirect.secret_path`."
|
||||
)
|
||||
|
||||
self.redirect_ttl_ms = self.parse_duration(redirect_config.get("ttl", "10m"))
|
||||
|
||||
self.media_upload_limits: List[MediaUploadLimit] = []
|
||||
for limit_config in config.get("media_upload_limits", []):
|
||||
time_period_ms = self.parse_duration(limit_config["time_period"])
|
||||
|
||||
@@ -126,9 +126,7 @@ class MediaRepository:
|
||||
cfg=hs.config.ratelimiting.remote_media_downloads,
|
||||
)
|
||||
|
||||
self._media_request_signature_secret = (
|
||||
b"supersecret" # TODO: make this configurable
|
||||
)
|
||||
self._media_request_signature_secret = hs.config.media.redirect_secret
|
||||
|
||||
# List of StorageProviders where we should search for media and
|
||||
# potentially upload to.
|
||||
@@ -1562,6 +1560,10 @@ class MediaRepository:
|
||||
This currently uses a HMAC-SHA256 signature encoded as hex, but this could
|
||||
be swapped to an asymmetric signature.
|
||||
"""
|
||||
assert self._media_request_signature_secret is not None, (
|
||||
"media request signature secret not set"
|
||||
)
|
||||
|
||||
# XXX: alternatively, we could do multiple rounds of HMAC with the
|
||||
# different segments, like AWS SigV4 does
|
||||
bytes_payload = "|".join(payload).encode("utf-8")
|
||||
@@ -1581,6 +1583,10 @@ class MediaRepository:
|
||||
|
||||
Returns True if the signature is valid, False otherwise.
|
||||
"""
|
||||
# In case there is no secret, we can't verify the signature
|
||||
if self._media_request_signature_secret is None:
|
||||
return False
|
||||
|
||||
bytes_payload = "|".join(payload).encode("utf-8")
|
||||
decoded_signature = bytes.fromhex(signature)
|
||||
computed_signature = hmac.digest(
|
||||
|
||||
Reference in New Issue
Block a user