Chocobozzz / PeerTube

ActivityPub-federated video streaming platform using P2P directly in your web browser
https://joinpeertube.org/
GNU Affero General Public License v3.0
13.09k stars 1.51k forks source link

CORS errors when serving video from S3 using official nginx template #3939

Closed LasersAreGreat closed 3 years ago

LasersAreGreat commented 3 years ago

I've setup Peertube to serve content directly from S3 buckets. Peertube works fine when I have the feature of serving from S3 turned off (those 3 lines in the nginx file commented out), but when I have those lines enabled I get CORS errors. Since it works with CURL, I'm inclined to think there might be a bug here, but I also think that an error on my part is entirely possible despite my days of effort to troubleshoot this.

I've made sure that I have the appropriate lines in my nginx configuration as per the official nginx file. I've enable CORS on the CDN in front of the s3 bucket. I've test it with CURL, and it works fine. But within peertube, I get a CORS error.

Here is the output from CURL, where it seems to work fine.

curl -I -X OPTIONS -H 'Origin: http://peertube.newsocial.tech' -H 'Access-Control-Request-Method: GET' 'https://peertubecdn.newsocial.tech/streaming-playlists/hls/224658e5-4e70-4fc2-b2c3-ebc7e02fc744/224658e5-4e70-4fc2-b2c3-ebc7e02fc744-720-fragmented.mp4'

HTTP/2 200 date: Wed, 07 Apr 2021 19:40:21 GMT content-type: video/mp4 content-length: 0 set-cookie: __cfduid=daaf1fb4c5c2de22ec889a754d1f58df81617824420; expires=Fri, 07-May-21 19:40:20 GMT; path=/; domain=.newsocial.tech; HttpOnly; SameSite=Lax; Secure access-control-allow-origin: http://peertube.newsocial.tech vary: Origin access-control-allow-methods: GET access-control-max-age: 1728000 x-amz-request-id: tx000000000000021bb44b4-00606e0aa4-962f868-nyc3c strict-transport-security: max-age=31536000; includeSubDomains; preload vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method x-hw: 1617824420.dop209.ez1.t,1617824420.cds208.ez1.shn,1617824420.cds208.ez1.sc,1617824421.cds208.ez1.p cf-cache-status: DYNAMIC cf-request-id: 094f72a936000074d748969000000001 expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" report-to: {"group":"cf-nel","endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=rpP7MVJHpPHgF%2FLLsxOk1pGjlYN1vcMY07B9WyUFrF9jo0Vz4pJoQcP1hZRjMpDVryZSOWRYSMzg1XZLGIa3sVYAA6pfu7RBAAJXiN7D56cpK925p7hjpoNY7Q%3D%3D"}],"max_age":604800} nel: {"max_age":604800,"report_to":"cf-nel"} x-content-type-options: nosniff server: cloudflare cf-ray: 63c5ba21efe174d7-EZE alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400

But, when peertube tries to load the video in the browser, I get CORS errors.

For example, loading this page: https://peertube.newsocial.tech/videos/watch/224658e5-4e70-4fc2-b2c3-ebc7e02fc744

Results in the following errors in the Chrome browser console:

224658e5-4e70-4fc2-b2c3-ebc7e02fc744:1 Access to XMLHttpRequest at 'https://peertubecdn.newsocial.tech/streaming-playlists/hls/224658e5-4e70-4fc2-b2c3-ebc7e02fc744/224658e5-4e70-4fc2-b2c3-ebc7e02fc744-720-fragmented.mp4' from origin 'https://peertube.newsocial.tech' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I'm running nginx 1.18.0 on Ubuntu 20.04, and here is the /etc/nginx/site-enabled/peertube file:

Minimum Nginx version required: 1.13.0 (released Apr 25, 2017)

Please check your Nginx installation features the following modules via 'nginx -V':

STANDARD HTTP MODULES: Core, Proxy, Rewrite, Access, Gzip, Headers, HTTP/2, Log, Real IP, SSL, Thread Pool, Upstream, AIO Multithreading.

THIRD PARTY MODULES: None.

server { listen 80; listen [::]:80; server_name peertube.newsocial.tech;

location /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/certbot; } location / { return 301 https://$host$request_uri; } }

upstream backend { server 127.0.0.1:9000; }

server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name peertube.newsocial.tech;

access_log /var/log/nginx/peertube.access.log; # reduce I/0 with buffer=10m flush=5m error_log /var/log/nginx/peertube.error.log;

Certificates

you need a certificate to run in production. see https://letsencrypt.org/

ssl_certificate /etc/letsencrypt/live/peertube.newsocial.tech/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/peertube.newsocial.tech/privkey.pem;

location ^~ '/.well-known/acme-challenge' { default_type "text/plain"; root /var/www/certbot; }

Security hardening (as of Nov 15, 2020)

based on Mozilla Guideline v5.6

ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256; # add ECDHE-RSA-AES256-SHA if you want compatibility with Android 4 ssl_session_timeout 1d; # defaults to 5m ssl_session_cache shared:SSL:40m; # estimated to 40k sessions ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on;

HSTS (https://hstspreload.org), requires to be copied in 'location' sections that have add_header directives

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";

Application

Application

  add_header Access-Control-Allow-Origin  '*';
  add_header Access-Control-Allow-Methods 'GET, OPTIONS';
  add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
  add_header Access-Control-Max-Age       1728000; # Preflight request can be cached 20 days

location @api { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;

client_max_body_size  100k; # default is 1M

proxy_connect_timeout 10m;
proxy_send_timeout    10m;
proxy_read_timeout    10m;
send_timeout          10m;

proxy_pass http://backend;

}

location / { try_files /dev/null @api; }

location = /api/v1/videos/upload { limit_except POST HEAD { deny all; }

# This is the maximum upload size, which roughly matches the maximum size of a video file.
# Note that temporary space is needed equal to the total size of all concurrent uploads.
# This data gets stored in /var/lib/nginx by default, so you may want to put this directory
# on a dedicated filesystem.
client_max_body_size                      50G; # default is 1M
add_header            X-File-Maximum-Size 20G always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)

try_files /dev/null @api;

}

location ~ ^/api/v1/(videos|video-playlists|video-channels|users/me) { client_max_body_size 3M; # default is 1M add_header X-File-Maximum-Size 2M always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)

try_files /dev/null @api;

}

Websocket

location @api_websocket { proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";

proxy_pass http://backend;

}

location /socket.io { try_files /dev/null @api_websocket; }

location /tracker/socket {

Peers send a message to the tracker every 15 minutes

# Don't close the websocket before then
proxy_read_timeout 15m; # default is 60s

try_files /dev/null @api_websocket;

}

Performance optimizations

For extra performance please refer to https://github.com/denji/nginx-tuning

root /var/www/peertube/s3fs;

Enable compression for JS/CSS/HTML, for improved client load times.

It might be nice to compress JSON/XML as returned by the API, but

leaving that out to protect against potential BREACH attack.

gzip on; gzip_vary on; gzip_types # text/html is always compressed by HttpGzipModule text/css application/javascript font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; gzip_min_length 1000; # default is 20 bytes gzip_buffers 16 8k; gzip_comp_level 2; # default is 1

client_body_timeout 30s; # default is 60 client_header_timeout 10s; # default is 60 send_timeout 10s; # default is 60 keepalive_timeout 10s; # default is 75 resolver_timeout 10s; # default is 30 reset_timedout_connection on; proxy_ignore_client_abort on;

tcp_nopush on; # send headers in one piece tcp_nodelay on; # don't buffer data sent, good for small data bursts in real time

If you have a small /var/lib partition, it could be interesting to store temp nginx uploads in a different place

See https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_temp_path

client_body_temp_path /var/www/peertube/storage/nginx/;

Bypass PeerTube for performance reasons. Optional.

Should be consistent with client-overrides assets list in /server/controllers/client.ts

location ~ ^/client/(assets/images/(icons/icon-36x36.png|icons/icon-48x48.png|icons/icon-72x72.png|icons/icon-96x96.png|icons/icon-144x144.png|icons/icon-192x192.png|icons/icon-512x512.png|logo.svg|favicon.png))$ { add_header Cache-Control "public, max-age=31536000, immutable"; # Cache 1 year

root /var/www/peertube;

try_files /storage/client-overrides/$1 /peertube-latest/client/dist/$1 @api;

}

Bypass PeerTube for performance reasons. Optional.

location ~ ^/client/(.*.(js|css|png|svg|woff2|otf|ttf|woff|eot))$ { add_header Cache-Control "public, max-age=31536000, immutable"; # Cache 1 year

alias /var/www/peertube/peertube-latest/client/dist/$1;

}

Bypass PeerTube for performance reasons. Optional.

location ~ ^/static/(thumbnails|avatars)/ { if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, OPTIONS'; add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; add_header Access-Control-Max-Age 1728000; # Preflight request can be cached 20 days add_header Content-Type 'text/plain charset=UTF-8'; add_header Content-Length 0; return 204; }

add_header Access-Control-Allow-Origin    '*';
add_header Access-Control-Allow-Methods   'GET, OPTIONS';
add_header Access-Control-Allow-Headers   'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
add_header Cache-Control                  "public, max-age=7200"; # Cache response 2 hours

rewrite ^/static/(.*)$ /$1 break;

try_files $uri @api;

}

Bypass PeerTube for performance reasons. Optional.

location ~ ^/static/(webseed|redundancy|streaming-playlists)/ {

 if ($request_method = 'OPTIONS') {
    add_header Access-Control-Allow-Origin  '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    #
    # Custom headers and headers various browsers *should* be OK with but aren't
    #
    add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
    #
    # Tell client that this pre-flight info is valid for 20 days
    #
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    return 204;
 }

  add_header 'Access-Control-Allow-Origin'  '*';
  add_header Access-Control-Allow-Methods 'GET, OPTIONS';
  add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
  add_header Access-Control-Max-Age       1728000; # Preflight request can be cached 20 days

limit_rate_after            5M;

# Clients usually have 4 simultaneous webseed connections, so the real limit is 3MB/s per client
set $peertube_limit_rate    800k;

# Increase rate limit in HLS mode, because we don't have multiple simultaneous connections
if ($request_uri ~ -fragmented.mp4$) {
  set $peertube_limit_rate  5M;
}

# Use this line with nginx >= 1.17.0
#limit_rate $peertube_limit_rate;
# Or this line if your nginx < 1.17.0
set $limit_rate $peertube_limit_rate;

  # Don't spam access log file with byte range requests
  access_log off;

# Enabling the sendfile directive eliminates the step of copying the data into the buffer
# and enables direct copying data from one file descriptor to another.
sendfile on;
sendfile_max_chunk 1M; # prevent one fast connection from entirely occupying the worker process. should be > 800k.
aio threads;

# Use this in tandem with fuse-mounting i.e. https://docs.joinpeertube.org/admin-remote-storage
# to serve files directly from a public bucket without proxying.
# Assumes you have buckets named after the storage subdirectories, i.e. 'videos', 'redundancy', etc.
set $cdn https://peertubecdn.newsocial.tech;
rewrite ^/static/webseed/(.*)$ $cdn/videos/$1 redirect;
rewrite ^/static/(.*)$         $cdn/$1        redirect;
rewrite ^/static/webseed/(.*)$ /videos/$1 break;
rewrite ^/static/(.*)$         /$1        break;

try_files $uri @api;
#try_files $uri /;

} }

finally, when I talk about the 3 lines that I can comment out to disable serving directly from s3 and make everything work, I mean these: set $cdn https://peertubecdn.newsocial.tech; rewrite ^/static/webseed/(.)$ $cdn/videos/$1 redirect; rewrite ^/static/(.)$ $cdn/$1 redirect;

LasersAreGreat commented 3 years ago

I found the problem, and it was not with Peertube but with the difference in how Digital Ocean handles their S3 competitors CORS setup compared to Amazon S3.

Anyone else having this problem can fix it by putting a * in the "Allowed Headers" field when setting up the CORS settings for digital ocean spaces.

LasersAreGreat commented 3 years ago

Since this is fixed by my above comment, I'm closing this.