# Handle redirects to S3 location ~ ^/internal/s3/(?[^/]+)/(?.*) { internal; include /etc/nginx/zulip-include/headers; add_header Content-Security-Policy "default-src 'none'; media-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;"; # The components of this path are originally double-URI-escaped # (see zerver/view/upload.py). "location" matches are on # unescaped values, which fills $s3_path with a properly # single-escaped path to pass to the upstream server. # (see associated commit message for more details) set $download_url https://$s3_hostname/$s3_path; proxy_set_header Host $s3_hostname; proxy_ssl_name $s3_hostname; proxy_ssl_server_name on; # Strip off X-amz-cf-id header, which otherwise the request has to # have been signed over, leading to signature mismatches. proxy_set_header x-amz-cf-id ""; # Strip off any auth request headers which the Zulip client might # have sent, as they will not work for S3, and will report an error due # to the signed auth header we also provide. proxy_set_header Authorization ""; proxy_set_header x-amz-security-token ""; # These headers are only valid if there is a body, but better to # strip them to be safe. proxy_set_header Content-Length ""; proxy_set_header Content-Type ""; proxy_set_header Content-MD5 ""; proxy_set_header x-amz-content-sha256 ""; proxy_set_header Expect ""; # Ensure that we only get _one_ of these response headers: the one # that Django added, not the one from S3. proxy_hide_header Cache-Control; proxy_hide_header Expires; proxy_hide_header Set-Cookie; # We are _leaving_ S3 to provide Content-Type, # Content-Disposition, and Accept-Ranges headers, which are the # three remaining headers which nginx would also pass through from # the first response. Django explicitly unsets the first, and # does not set the latter two. # We slice the content into 5M chunks; this means that the client # doesn't need to wait for nginx to download and cache the full # content if the client just requested a small range (e.g. for # showing a thumbnail of a large video). 5M is chosen to be # enough for videos to be able to thumbnail in one slice, but not # take overly long to retrieve from S3, or cause overwhelming # numbers of cache entries for large files. slice 5m; proxy_set_header Range $slice_range; proxy_pass $download_url$is_args$args; proxy_cache uploads; # If the S3 response doesn't contain Cache-Control headers (which # we don't expect it to) then we assume they are valid for a very # long time. The size of the cache is controlled by # `s3_disk_cache_size` and read frequency, set via # `s3_cache_inactive_time`. proxy_cache_valid 200 206 1y; # We only include the requested content-disposition (and range # slice) in the cache key, so that we cache "Content-Disposition: # attachment" separately from the inline version. proxy_cache_key $download_url$s3_disposition_cache_key$slice_range; } # Internal file-serving location /internal/local/uploads { internal; include /etc/nginx/zulip-include/headers; add_header Content-Security-Policy "default-src 'none'; media-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;"; # Django handles setting Content-Type, Content-Disposition, and Cache-Control. alias /home/zulip/uploads/files; } location /internal/local/user_avatars { internal; include /etc/nginx/zulip-include/headers; add_header Content-Security-Policy "default-src 'none'; img-src 'self'"; include /etc/nginx/zulip-include/uploads.types; alias /home/zulip/uploads/avatars; }