feat: add configurable HLS CORS allowlists

This commit is contained in:
root
2026-09-04 22:28:33 +02:00
parent 6fae6a3a43
commit 9710e8d228
24 changed files with 879 additions and 7 deletions
+42 -4
View File
@@ -95,6 +95,40 @@ Configuration is read from the process environment when AzuraCast builds its con
| `AZURACAST_ONDEMAND_HLS_AUDIO_BITRATE` | `128k` | `10k`–`9999k` in the exact form `[1-9][0-9]{1,3}k` | AAC target bitrate and part of the rendition cache key. Use a sensible audio bitrate despite the broad validation range. |
| `AZURACAST_ONDEMAND_HLS_TRANSCODE_TIMEOUT` | `600` | `30`–`3600` seconds | Maximum synchronous FFmpeg run time. |
### Cross-origin HLS playback (CORS)
Cross-origin playback is **disabled by default**. Configure an explicit allowlist per station with the plugin commands; do not edit generated Nginx files. The setting is stored in the AzuraCast database by the plugin, and every modifying command rewrites that station's generated Nginx configuration and reloads Nginx only when the file changed.
First, run the plugin migration once after installing/upgrading the plugin:
```bash
cd /var/azuracast
./docker.sh cli doctrine:migrations:migrate --no-interaction
```
Then configure the station (the station argument accepts its ID or short name):
```bash
# Replace the entire allowlist with the two approved web origins.
./docker.sh cli azuracast:ondemand-hls:cors:set 11 \
https://aifrequency.org \
https://www.aifrequency.org
# Inspect the current allowlist.
./docker.sh cli azuracast:ondemand-hls:cors:list 11
# Add or remove one origin without changing the others.
./docker.sh cli azuracast:ondemand-hls:cors:add 11 https://player.example.org
./docker.sh cli azuracast:ondemand-hls:cors:remove 11 https://player.example.org
# Disable cross-origin playback for this station again.
./docker.sh cli azuracast:ondemand-hls:cors:clear 11
```
Origins must be absolute `http://` or `https://` origins without paths, credentials, query strings, or fragments. They are normalized before being stored. The plugin emits an exact `Access-Control-Allow-Origin` value for the requesting allowed origin; it never joins multiple origins and never substitutes `*`. It also emits `Vary: Origin`, supports `GET`, `HEAD`, and token-authorized `OPTIONS` preflight requests (including the `Range` request header), and leaves requests with no `Origin` header unchanged.
The allowlist is generated into the plugin's protected internal Nginx HLS location, after the authorized `X-Accel-Redirect`. It therefore covers `master.m3u8`, `media.m3u8`, and HLS `.ts` segments rather than only the initial PHP authorization response. The bearer URL remains required for every resource request; allowing an origin does not make a resource public or extend its expiry.
Example Docker override fragment:
```yaml
@@ -247,7 +281,7 @@ The safest deployment is same-origin playback through AzuraCast without a CDN in
- Preserve the complete path and do not normalize or rewrite token/resource components.
- Preserve AzuraCast's `X-Accel-Redirect` processing at its internal Nginx layer. An external proxy should forward the resulting body; it must not expose or independently interpret `/internal/stations`.
- Redact bearer URLs from proxy/CDN/WAF logs as described above.
- The plugin does not emit `Access-Control-Allow-Origin`. Cross-origin players will fail unless CORS is added deliberately at a trusted proxy. Prefer same-origin; if enabling CORS, allow only required origins and still disable caching/logging.
- Cross-origin playback is disabled until an explicit station allowlist is configured with `azuracast:ondemand-hls:cors:*`. Prefer same-origin; if enabling CORS, allow only required HTTPS origins and still disable caching/logging. Do not add wildcard headers at a proxy, because that would override the plugin's explicit-origin policy.
- Avoid redirects to another hostname: the full token appears in the `Location` request path and may be logged. Set AzuraCast's external/base URL correctly so the API returns the final HTTPS origin.
- Keep proxy read timeouts above the expected synchronous first-generation time, up to `TRANSCODE_TIMEOUT`, or create/warm sessions from a backend job before giving the URL to a client.
@@ -277,7 +311,7 @@ composer lint
composer test
```
The test suite covers configuration safety, opaque-token format/mutation, resource allowlisting and traversal/symlink rejection, station/expiry authorization, session limits, and cleanup behavior.
The test suite covers configuration safety, opaque-token format/mutation, resource allowlisting and traversal/symlink rejection, station/expiry authorization, session limits, cleanup behavior, CORS origin validation, exact allowlist matching for both approved origins, rejected/missing origins, preflight headers, and generated Nginx CORS rules.
## Troubleshooting
@@ -311,10 +345,14 @@ The authorized cache file is outside a path recognized by `App\Nginx\CustomUrls:
### Player loads the master playlist but fails on media/segments
- Check browser developer tools for `403`, `429`, CORS, mixed-content, or proxy-cache errors.
- For a browser hosted at `https://aifrequency.org`, configure both required origins if applicable and verify them with:
```bash
./docker.sh cli azuracast:ondemand-hls:cors:list 11
```
The output must list `https://aifrequency.org` and, for the `www` site, `https://www.aifrequency.org` exactly.
- Run the CORS command again after plugin installation/upgrades if a station's generated Nginx configuration was removed; each modifying command regenerates it automatically.
- The asset rate limit is 150 requests per 5 seconds; investigate retry loops or unusually short segments if it is reached.
- Use same-origin URLs unless CORS is explicitly configured.
- Ensure proxies preserve relative playlist resolution and do not cache assets.
- Confirm the station Nginx config was regenerated after installation.
### Sessions remain at the limit