Downloads from external storages and federated shares time out with Conten Security Policy error

I’m running a nextcloud 19.04 instance on plesk and set it up according to the documentation and this how-to https://cry-sys.de/article/2-nextcloud-17-unter-plesk-installieren/
It all works great except for downloads from external storages and federated shares. I can upload to them but I can’t download anything. It doesn’t matter if it is a federated share, a nextcloud external storage or an s3 external storage.

Every download attempt times out and throws Content Security Errors in the browser web console:

GEThttps://files.example.de/remote.php/webdav/Test/openttd-1.10.3-windows-win64(1).exe
[HTTP/1.1 500 Internal Server Error 30978ms]

	
GET
	https://files.example.de/remote.php/webdav/Test/openttd-1.10.3-windows-win64(1).exe
Status500
Internal Server Error
VersionHTTP/1.1
Übertragen14,90 KB (14,08 KB Größe)

    	
    HTTP/1.1 500 Internal Server Error

    Server: nginx

    Date: Tue, 13 Oct 2020 15:56:08 GMT

    Content-Type: text/html; charset=UTF-8

    Transfer-Encoding: chunked

    Connection: keep-alive

    Expires: Thu, 19 Nov 1981 08:52:00 GMT

    Cache-Control: no-store, no-cache, must-revalidate

    Pragma: no-cache

    Referrer-Policy: no-referrer

    X-Content-Type-Options: nosniff

    X-Download-Options: noopen

    X-Frame-Options: SAMEORIGIN

    X-Permitted-Cross-Domain-Policies: none

    X-Robots-Tag: none

    X-XSS-Protection: 1; mode=block

    Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'

    Strict-Transport-Security: max-age=15768000; includeSubDomains; preload;
    	
    Accept
    	text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Encoding
    	gzip, deflate, br
    Accept-Language
    	de,en-US;q=0.7,en;q=0.3
    Connection
    	keep-alive
    Cookie
    	__Host-nc_sameSiteCookielax=true; __Host-nc_sameSiteCookiestrict=true; oc7oszrtzdsr=rum16rouse7p5om5sta1gj33m8; oc_sessionPassphrase=%2B5UoEBVwpoZ63gHHtaVZaSD129izYGQ1FOT7gncVZm53dGB%2FG9SseKuePQCVUiIgVrgnkiFl4Qc%2BPk6JlHh9lB7bbzOuPq1WrXDm52NDTeKRBD%2BpHDEBx%2BhGzSbL3d29
    DNT
    	1
    Host
    	files.example.de
    Upgrade-Insecure-Requests
    	1
    User-Agent
    	Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0

Content Security Policy: Die Einstellungen der Seite haben das Laden einer Ressource auf inline blockiert ("script-src"). openttd-1.10.3-windows-win64(1).exe:32:1
Content Security Policy: Die Einstellungen der Seite haben das Laden einer Ressource auf data: blockiert ("media-src").
JQMIGRATE: Migrate is installed, version 1.4.1 jquery-migrate.min.js:2:551
$ is deprecated: The global jQuery is deprecated. It will be updated to v2.4 in Nextcloud 20 and v3.x in Nextcloud 21. In later versions of Nextcloud it might be removed completely. Please ship your own. globals.js:61:15
session heartbeat polling started session-heartbeat.js:97:9
$ is deprecated: The global jQuery is deprecated. It will be updated to v2.4 in Nextcloud 20 and v3.x in Nextcloud 21. In later versions of Nextcloud it might be removed completely. Please ship your own. 2 globals.js:61:15

The response csp header looks actually good to me but somehow it still doesnt work.
Maybe related or unrelated, running curl -vvvv https://files.example.com answers with

[ssl/tls stuff removed]
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx
< Date: Tue, 13 Oct 2020 15:55:19 GMT
< Content-Type: text/html
< Content-Length: 156
< Last-Modified: Tue, 13 Oct 2020 15:26:00 GMT
< Connection: keep-alive
< ETag: "5f85c708-9c"
< X-Powered-By: PleskLin
< Strict-Transport-Security: max-age=15768000; includeSubDomains; preload;
< Strict-Transport-Security: max-age=15768000; includeSubDomains; preload;
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
        <script> window.location.href="index.php"; </script>
        <meta http-equiv="refresh" content="0; URL=index.php">
</head>
</html>

while running curl -vvvv https://files.example.com/index.php answers with:

> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx
< Date: Tue, 13 Oct 2020 16:06:24 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: oc7oszrtzdsr=tpu7ds3p69rs8do4ps52797l32; path=/; secure; HttpOnly; SameSite=Lax
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Set-Cookie: oc_sessionPassphrase=yio8JZP4Jj4ORoP1gpxmzwIb7Yz37tY8lBs5AnhH0d09iL8AGgfG5yhCGtCR9ylNFel1zNHJ04aDLage%2F%2F3S76SOjVx1mst%2FApwYLvQzGnOcg1Gn3Q7cL%2BAItsKDGxhw; path=/; secure; HttpOnly; SameSite=Lax
< Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-ME5UNTRDZmFrY3N4aHpva09NUXkvK2x1NDJUNWpQVnYvcGh5eFJUeWNNQT06aDd5bzFFNkszcElKMW5aTmE2SUJ5Sk1vdWpTOXpaUVdyZkZIalVPRkVZWT0='; style-src 'self' 'unsafe-inline'; frame-src *; img-src * data: blob:; font-src 'self' data:; media-src *; connect-src *; object-src 'none'; base-uri 'self';
< Referrer-Policy: no-referrer
< X-Content-Type-Options: nosniff
< X-Download-Options: noopen
< X-Frame-Options: SAMEORIGIN
< X-Permitted-Cross-Domain-Policies: none
< X-Robots-Tag: none
< X-XSS-Protection: 1; mode=block
< Set-Cookie: __Host-nc_sameSiteCookielax=true; path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax
< Set-Cookie: __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=strict
< Location: https://files.example.com/index.php/login
< X-Powered-By: PleskLin
< Strict-Transport-Security: max-age=15768000; includeSubDomains; preload;
< Strict-Transport-Security: max-age=15768000; includeSubDomains; preload;
<

I would have expected that https://files.example.com would also answer with the correct csp header because that’s what my instance at home does.

Nginx additional configuration in plesk:

# Add headers to serve security related headers
# Before enabling Strict-Transport-Security headers please read into this
# topic first.

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;

# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.

# Remove X-Powered-By, which is an information leak

fastcgi_hide_header X-Powered-By;

location = /robots.txt {
	allow all;
	log_not_found off;
	access_log off;
}

# Make a regex exception for `/.well-known` so that clients can still
# access it despite the existence of the regex rule
# `location ~ /(\.|autotest|...)` which would otherwise handle requests
# for `/.well-known`.
location ^~ /.well-known {
	# The following 6 rules are borrowed from `.htaccess`

	rewrite ^/\.well-known/host-meta\.json  /public.php?service=host-meta-json  last;
	rewrite ^/\.well-known/host-meta        /public.php?service=host-meta       last;
	rewrite ^/\.well-known/webfinger        /public.php?service=webfinger       last;
	rewrite ^/\.well-known/nodeinfo         /public.php?service=nodeinfo        last;

	location = /.well-known/carddav     { return 301 /remote.php/dav/; }
	location = /.well-known/caldav      { return 301 /remote.php/dav/; }

	try_files $uri $uri/ =404;
}

# Enable gzip but do not remove ETag headers

gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

# Uncomment if your server is build with the ngx_pagespeed module
# This module is currently not supported.
#pagespeed off;

# Rules borrowed from `.htaccess` to hide certain paths from clients
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)              { return 404; }

# Adding the cache control header for js, css and map files
# Make sure it is BELOW the PHP block
# Add headers to serve security related headers (It is intended to
# have those duplicated to the ones above)
# Before enabling Strict-Transport-Security headers please read into
# this topic first.

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;

# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.

# Adding the cache control header for js, css and map files
# Make sure it is BELOW the PHP block
location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
	try_files $uri /index.php$request_uri;
	add_header Cache-Control "public, max-age=15778463";
	# Add headers to serve security related headers  (It is intended
	# to have those duplicated to the ones above)
	# Before enabling Strict-Transport-Security headers please read
	# into this topic first.
	add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
	#
	# WARNING: Only add the preload option once you read about
	# the consequences in https://hstspreload.org/. This option
	# will add the domain to a hardcoded list that is shipped
	# in all major browsers and getting removed from this list
	# could take several months.
	add_header Referrer-Policy no-referrer;
	add_header X-Content-Type-Options nosniff;
	add_header X-Download-Options noopen;
	add_header X-Frame-Options SAMEORIGIN;
	add_header X-Permitted-Cross-Domain-Policies none;
	add_header X-Robots-Tag none;
	add_header X-XSS-Protection "1; mode=block" always;

	# Optional: Don't log access to assets
	access_log off;
}

location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ {
	try_files $uri /index.php$request_uri;
	# Optional: Don't log access to other assets
	access_log off;
}

Remaining nginx config from plesk in background:

#ATTENTION!
#
#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,
#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED.

server {
        listen <ip>:443 ssl;

        server_name files.example.de;
        server_name www.files.example.de;
        server_name ipv4.files.example.de;

        ssl_certificate             /opt/psa/var/certificates/scf69MuUN;
        ssl_certificate_key         /opt/psa/var/certificates/scf69MuUN;

        client_max_body_size 128m;

        proxy_read_timeout 1200;

        root "/var/www/vhosts/example.de/files.example.de";
        access_log "/var/www/vhosts/system/files.example.de/logs/proxy_access_ssl_log";
        error_log "/var/www/vhosts/system/files.example.de/logs/proxy_error_log";

        #extension letsencrypt begin
        location ^~ /.well-known/acme-challenge/ {
                root /var/www/vhosts/default/htdocs;

                types { }
                default_type text/plain;

                satisfy any;
                auth_basic off;
                allow all;

                location ~ ^/\.well-known/acme-challenge.*/\. {
                        deny all;
                }
        }
        #extension letsencrypt end

        #extension sslit begin

        #extension sslit end

        location ~ /\.ht {
                deny all;
        }

        location ~ ^/(plesk-stat|awstats-icon|webstat|webstat-ssl|ftpstat|anon_ftpstat) {
                auth_basic "Domain statistics";
                auth_basic_user_file "/var/www/vhosts/system/files.example.de/pd/d..httpdocs@plesk-stat";
                autoindex on;

                location ~ ^/plesk-stat(.*) {
                        alias /var/www/vhosts/system/files.example.de/statistics/$1;
                }

                location ~ ^/awstats-icon(.*) {
                        alias /usr/share/awstats/icon/$1;
                }

                location ~ ^/(.*) {
                        alias /var/www/vhosts/system/files.example.de/statistics/$1;
                }
        }

        location ~ ^/~(.+?)(/.*?\.php)(/.*)?$ {
                fastcgi_read_timeout 1200;
                alias /var/www/vhosts/example.de/web_users/$1/$2;
                fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
                fastcgi_param PATH_INFO $fastcgi_path_info;
                fastcgi_pass "unix:///var/www/vhosts/system/files.example.de/php-fpm.sock";
                include /etc/nginx/fastcgi.conf;

        }

        location ~ \.php(/.*)?$ {
                fastcgi_read_timeout 1200;
                fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
                fastcgi_param PATH_INFO $fastcgi_path_info;
                fastcgi_pass "unix:///var/www/vhosts/system/files.example.de/php-fpm.sock";
                include /etc/nginx/fastcgi.conf;

        }

        location ~ /$ {
                index "index.html" "index.cgi" "index.pl" "index.php" "index.xhtml" "index.htm" "index.shtml";
        }

        add_header X-Powered-By PleskLin;

        include "/var/www/vhosts/system/files.example.de/conf/vhost_nginx.conf";
}

server {
        listen <ip>:80;

        server_name files.example.de;
        server_name www.files.example.de;
        server_name ipv4.files.example.de;

        client_max_body_size 128m;

        proxy_read_timeout 1200;

        location / {
                return 301 https://$host$request_uri;
        }

I’d be so greatful if anyone could help me with this!

Anyone got any ideas?
It’s frustrating not being able to use federated shares and external storages.

Content Security Policy: Die Einstellungen der Seite haben das Laden einer Ressource auf inline blockiert (“script-src”). openttd-1.10.3-windows-win64(1).exe:32:1

means you somewhere use an inline-scripts (there are 3 kinds of these) wchich do not falls under ‘nonce-M…alVPRkVZWT0=’ allowance.

  • If it is <script>...</script> constricts - you’ll need to add nonce= attribute to it:
    <script nonce='M...alVPRkVZWT0='>...</script> - all your long base64 ‘nonce-value’ from CSP header.
  • If it is an inline event handler in the tag like <tag onClick='...some javascrtipt here...' - you need to add its ‘hash-value’ to the CSP header. Instead of Firefox do use Chrome browser - it counts ‘sha256-hash’ for all inline scripts.
    Add this 'sha256-value' 'unsafe-hashes' to the script-src directive.
  • if it is an javascript:-navigation - there is only way to remove ‘nonce-M…alVPRkVZWT0=’ token from the CSP header and insert ‘unsafe-inline’ token (which is reduced effectiveness of CSP)

Content Security Policy: Die Einstellungen der Seite haben das Laden einer Ressource auf data: blockiert (“media-src”).

means you use data:-urls for media-sources therefore it need to add data: to the “media-src” directive, rules should be:
media-src * data:;

Thank you for your answer!
I am not using any modified files so there are no custom inline-scripts. It is a standard installation.

I even updated to Nextcloud 20 now by removing everything except /config /data /3rdparty but it still doesn’t work.
On a different server but with the same installation method and nginx/php configuration it works. So I’m somehow lost.

Every download attempt times out and throws Content Security Errors in the browser web console:

Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'

while running curl -vvvv https://files.example.com/index.php answers with:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-ME5UNTRDZmFrY3N4aHpva09NUXkvK2x1NDJUNWpQVnYvcGh5eFJUeWNNQT06aDd5bzFFNkszcElKMW5aTmE2SUJ5Sk1vdWpTOXpaUVdyZkZIalVPRkVZWT0='; style-src 'self' 'unsafe-inline'; frame-src *; img-src * data: blob:; font-src 'self' data:; media-src *; connect-src *; object-src 'none'; base-uri 'self';

In the startpost you shown 2 CSPs and those are differ. So you do have 2 different configs. I do not see the CSP in the nginx config, so you have publish it somewhere else.

At least you could to comment out CSP for a while to check is the problem in it or not.

I found the problem of that difference, calling files.example.com returns the index.html instead of the index.php, so I renamed index.html. That is now gone. However, didn’t change my download problem.
The CSP header is sent by nextcloud, it’s not in the nginx config.

I started 2 more nextcloud instances on 2 different hosts to test the behaviour of the federated sharing to find out if one server has wrong configs. This just lead to a strange mess…
4 server: files, trans, 1blu and kevin. kevin is the one that sent the share to all others.
files and trans running on the same host both can’t download any files.
1blu running the same configuration as files and trans has no trouble downoading the files.
(shares between 1blu and files and trans all work correctly)

There doesn’t seem to be any logic to the problem unless the problem is buried in some config I can’t find easily because in plesk the php and the nginx config is exactly the same for files, trans and 1blu. So I don’t know why they behave differently. Or maybe it is the config of kevin but why would that config behave differently for 2 different hosts (who are both configured in the exact same way, except that files and trans are subdomains while 1blu is not).

Maybe I’ll figure it out some day or you have another tip.

(However, the test revealed that nextcloud really managed to screw up their federated sharing. Deletion never works [known bug in nextcloud 20 iirc] and even uploads throw errors [known too iirc] even though they seem to work. So federated sharing is not useable at the moment anyway. Hopefully they’ll fix it in nextcloud 20.0.1)

If you do not have any Content Security Policy errors in browser console, this part of puzzle is solved.

AFAIK the subdomains should be specified as trusted domains in config/config.php.
May be you aet wrong access rights to www-data /usr/share/nextcloud folder.

Anyway, it need to check nextcloud logs it should be some errors/warnings there. Set Logging level to debug it could help to localise the problem.

I didn’t say that. Only the curl difference to the root or /index.php is solved.

If it wasn’t specified in there, I wouldn’t even have access to nextcloud.

I would get all kinds of errors if that were true, not only errors on federated downloads.

Yeah maybe with debug I’ll get some more info.