docker icon indicating copy to clipboard operation
docker copied to clipboard

Cannot fix warning message "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy. If not, this is a security issue and can allow an attacker to spoof their IP address as visible to the Nextcloud. Further information can be found in the documentation."

Open NaGeL182 opened this issue 4 years ago • 7 comments

No matter what I can't get rid of this warning message
The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy. If not, this is a security issue and can allow an attacker to spoof their IP address as visible to the Nextcloud. Further information can be found in the documentation.

I'm using traefik 2.3 with this static setting:

entryPoints:
  web:
    address: ":80"
# If you set up a new service you need to open port 80 for CERT
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

api:
  dashboard: true

providers:
  docker:
    # Do not expose containers unless explicitly told so
    exposedByDefault: false

#log:
#  level: DEBUG
#  filePath: "/var/logs/traefik/traefik.log"

#accessLog:
#  filePath: "/var/logs/traefik/access.log"

certificatesResolvers:
  letsencrypt:
    acme:
      #comment the line below for live server
      #caserver: https://acme-staging-v02.api.letsencrypt.org/directory
      email: [email protected]
      storage: /etc/traefik/acme.json
      httpChallenge:
        # used during the challenge
        entryPoint: web

and my docker-compose:

version: '3.5'
#USE .env!!!
services:
  db:
    image: mariadb
    container_name: nextcloud_db
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=asecret
    env_file:
      - db.env

  redis:
    image: redis:alpine
    container_name: nextcloud_redis
    restart: always

  app:
    image: nextcloud:fpm-alpine
    container_name: nextcloud_app
    restart: always
    volumes:
      - nextcloud:/var/www/html
    environment:
      - MYSQL_HOST=db
      - REDIS_HOST=redis
    env_file:
      - db.env
    depends_on:
      - db
      - redis

  web:
    image: nginx:alpine
    container_name: nextcloud_web
    restart: always
    ports:
      - "9999:80"
    expose:
      - '443'
    volumes:
      - nextcloud:/var/www/html:ro
      - ./web/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    labels: 
      # Explicitly tell Traefik to expose this container
      - traefik.enable=true
      - traefik.http.routers.nextcloud.rule=Host(`${NEXTCLOUDHOST}`)
      - traefik.http.services.nextcloud.loadbalancer.server.port=80
      - traefik.http.routers.nextcloud.tls=true
      - traefik.http.routers.nextcloud.tls.certresolver=letsencrypt
      - traefik.http.routers.nextcloud.middlewares=nn-header,nextcloud-dav
      - traefik.http.middlewares.nn-header.headers.customRequestHeaders.X-Forwarded-Proto=https
      - traefik.http.middlewares.nn-header.headers.sslredirect=true
      - traefik.http.middlewares.nn-header.headers.STSSeconds=15552000
      - traefik.http.middlewares.nn-header.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.nn-header.headers.accessControlAllowOriginList=wss://${NEXTCLOUDHOST},https://${NEXTCLOUDHOST}
      - traefik.http.middlewares.nn-redirect.redirectregex.permanent=true
      - traefik.http.middlewares.nextcloud-dav.replacepathregex.regex=^/.well-known/ca(l|rd)dav
      - traefik.http.middlewares.nextcloud-dav.replacepathregex.replacement=/remote.php/dav/      
    networks: 
      default:
      cloud_managment:
        aliases: 
          - nextcloud

  onlyoffice:
    image: onlyoffice/documentserver
    container_name: nextcloud_onlyoffice
    restart: always
    stdin_open: true
    expose: 
      - '80'
      - '443'
    tty: true
    volumes: 
      - ./onlyoffice/log:/var/log/onlyoffice
      - ./onlyoffice/Data:/var/www/onlyoffice/Data
      - ./onlyoffice/onlyoffice:/var/lib/onlyoffice
      - ./onlyoffice/postgresql:/var/lib/postgresql
    labels: 
      # Explicitly tell Traefik to expose this container
      - traefik.enable=true
      - traefik.http.routers.onlyoffice.rule=Host(`${ONLYOFFICEHOST}`)
      - traefik.http.middlewares.oo-header.headers.customRequestHeaders.X-Forwarded-Proto=https
      - traefik.http.middlewares.oo-header.headers.accessControlAllowOriginList=*
      - traefik.http.routers.onlyoffice.middlewares=oo-header
      - traefik.http.routers.onlyoffice.tls=true
      - traefik.http.routers.onlyoffice.tls.certresolver=letsencrypt
    networks: 
      default:
      cloud_managment:

  cron:
    image: nextcloud:fpm-alpine
    container_name: nextcloud_cron
    restart: always
    volumes:
      - nextcloud:/var/www/html
    entrypoint: /cron.sh
    depends_on:
      - db
      - redis

networks:
  default:
    name: nextcloud
  cloud_managment:
    external: true


volumes:
  db:
  nextcloud:

my config.php has these:

'trusted_domains' =>
  array (
    0 => 'nextcloud.example.com',
    1 => 'nextcloud_web',
    2 => 'traefik',
  ),
  'forwarded-for-headers' =>
  array (
    0 => 'X-Forwarded-For',
    1 => 'HTTP_X_FORWARDED_FOR',
  ),
'overwriteprotocol' => 'https',
  'overwritehost' => 'nextcloud.example.com',
  'overwrite.cli.url' => 'https://nextcloud.example.com',

my ngix.conf:

worker_processes auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    upstream php-handler {
        server app:9000;
    }

    map $http_host $this_host {
        "" $host;
        default $http_host;
    }

    map $http_x_forwarded_proto $the_scheme {
        default $http_x_forwarded_proto;
        "" $scheme;
    }

    map $http_x_forwarded_host $the_host {
        default $http_x_forwarded_host;
        "" $this_host;
    }

    server {
        listen 80;
    
        # 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.
        # Before enabling Strict-Transport-Security headers please read into this
        # topic first.
        #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
        #
        # Add headers to serve security related headers
        add_header Referrer-Policy                      "no-referrer"   always;
        add_header X-Content-Type-Options               "nosniff"       always;
        add_header X-Download-Options                   "noopen"        always;
        add_header X-Frame-Options                      "SAMEORIGIN"    always;
        add_header X-Permitted-Cross-Domain-Policies    "none"          always;
        add_header X-Robots-Tag                         "none"          always;
        add_header X-XSS-Protection                     "1; mode=block" always;

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

        # Path to the root of your installation
        root /var/www/html;

        # Specify how to handle directories -- specifying `/index.php$request_uri`
        # here as the fallback means that Nginx always exhibits the desired behaviour
        # when a client requests a path that corresponds to a directory that exists
        # on the server. In particular, if that directory contains an index.php file,
        # that file is correctly served; if it doesn't, then the request is passed to
        # the front-end controller. This consistent behaviour means that we don't need
        # to specify custom rules for certain paths (e.g. images and other assets,
        # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
        # `try_files $uri $uri/ /index.php$request_uri`
        # always provides the desired behaviour.
        index index.php index.html $scheme://$host:$server_port/index.php$request_uri;

        # Rule borrowed from `.htaccess` to handle Microsoft DAV clients
        location = / {
            if ( $http_user_agent ~ ^DavClnt ) {
                return 302 $scheme://$host:$server_port/remote.php/webdav/$is_args$args;
            }
        }

        location ~* ^/ds-vpath/ {
		rewrite /ds-vpath/(.*) /$1  break;
                proxy_pass http://nextcloud_onlyoffice;
                proxy_redirect     off;

                client_max_body_size 100m;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";

                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $the_host/ds-vpath;
                proxy_set_header X-Forwarded-Proto $the_scheme;
        }

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

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

        # The following 2 rules are only needed for the user_webfinger app.
        # Uncomment it if you're planning to use this app.
        #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
        #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;

        # The following rule is only needed for the Social app.
        # Uncomment it if you're planning to use this app.
        #rewrite ^/.well-known/webfinger /public.php?service=webfinger last;

        # 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  $scheme://$host:$server_port/public.php?service=host-meta-json  last;
#            rewrite ^/\.well-known/host-meta        $scheme://$host:$server_port/public.php?service=host-meta       last;
#            rewrite ^/\.well-known/webfinger        $scheme://$host:$server_port/public.php?service=webfinger       last;
#            rewrite ^/\.well-known/nodeinfo         $scheme://$host:$server_port/public.php?service=nodeinfo        last;
#
#            location = /.well-known/carddav     { return 301 $scheme://$host:$server_port/remote.php/dav/; }
#            location = /.well-known/caldav      { return 301 $scheme://$host:$server_port/remote.php/dav/; }

#            try_files $uri $uri/ =404;
#        }
        # 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`

            location = /.well-known/carddav     { return 301 $scheme://$host:$server_port/remote.php/dav/; }
            location = /.well-known/caldav      { return 301 $scheme://$host:$server_port/remote.php/dav/; }
            location = /.well-known/webfinger   { return 301 $scheme://$host:$server_port/public.php?service=webfinger; }
            # Anything else is dynamically handled by Nextcloud
            location ^~ /.well-known            { return 301 $scheme://$host:$server_port/index.php$uri; }

            try_files $uri $uri/ =404;
        }
        #location = /.well-known/carddav {
        #    return 301 $scheme://$host:$server_port/remote.php/dav;
        #}

        #location = /.well-known/caldav {
        #    return 301 $scheme://$host:$server_port/remote.php/dav;
        #}

        # set max upload size
        client_max_body_size 10G;
        fastcgi_buffers 64 4K;

        # 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;

        location / {
            rewrite ^ /index.php;
        }

        location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
            deny all;
        }
        location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
            deny all;
        }

        location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
            fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
            set $path_info $fastcgi_path_info;
            try_files $fastcgi_script_name =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $path_info;
            # fastcgi_param HTTPS on;

            # Avoid sending the security headers twice
            fastcgi_param modHeadersAvailable true;

            # Enable pretty urls
            fastcgi_param front_controller_active true;
            fastcgi_pass php-handler;
            fastcgi_intercept_errors on;
            fastcgi_request_buffering off;
        }

        location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
            try_files $uri/ =404;
            index index.php;
        }

        # 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" always;
            add_header X-Content-Type-Options "nosniff" always;
            add_header X-Download-Options "noopen" always;
            add_header X-Frame-Options "SAMEORIGIN" always;
            add_header X-Permitted-Cross-Domain-Policies "none" always;
            add_header X-Robots-Tag "none" always;
            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|mp4|webm)$ {
            try_files $uri /index.php$request_uri;
            # Optional: Don't log access to other assets
            access_log off;
        }
    }
}

I have no idea what I am doing wrong anymore

NaGeL182 avatar Jan 24 '21 17:01 NaGeL182

Not a pro, but I would guess your trusted_domain is to be blamed. This has to be set to the external url of your server

supermar1010 avatar Jan 25 '21 19:01 supermar1010

Not a pro, but I would guess your trusted_domain is to be blamed. This has to be set to the external url of your server

@supermar1010 that would be nextcloud.example.com or should I put example.com as well?

NaGeL182 avatar Jan 25 '21 20:01 NaGeL182

My nextcloud domain is: cloud.example.com.

My trusted_domains look like this:

  'trusted_domains' => 
  array (
    0 => 'localhost',
    1 => 'cloud.example.com',
  ),

supermar1010 avatar Jan 25 '21 21:01 supermar1010

sadly didn't solve it, when I added localhost :(

NaGeL182 avatar Jan 25 '21 21:01 NaGeL182

It should be the ip address of the nginx:alpine container. That the proxy. Mine was 'trusted_domains' => array ( 0 => '192.168.1.10', 1 => 'cloud.example.com', ),

Set a static Ip address for it

shreyasajj avatar Feb 04 '21 23:02 shreyasajj

I solved this problem by assigning a static IP to the traefik proxy network and putting this IP range as trusted proxy in the nextcloud configuration:

docker-compose for traefik:

version: '3.7'

networks:
  proxy:
    name: traefik_proxy
    ipam:
      config:
        - subnet: 172.33.0.0/16

services:

  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    networks:
      - proxy
    volumes:
      - "./config/traefik.toml:/etc/traefik/traefik.toml:ro"
      - "./config/dynamic.toml:/etc/traefik/dynamic.toml:ro"
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

docker-compose for nextcloud:

version: '3.7'

networks:
  nextcloud:
  proxy:
    external:
      name: traefik_proxy

services:
  db:
    image: mariadb
    restart: always
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - "./db:/var/lib/mysql"
    networks:
      - nextcloud
    environment:
      - MYSQL_ROOT_PASSWORD=passwordpassword
      - MYSQL_PASSWORD=password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  redis:
    image: redis:latest
    restart: always
    networks:
      - nextcloud
    volumes:
      - "./redis:/var/lib/redis"

  app:
    image: nextcloud:latest
    restart: always
    networks:
      - nextcloud
      - proxy
    depends_on:
      - db
      - redis
    volumes:
      - "./nextcloud:/var/www/html"
      - "./apps:/var/www/html/custom_apps"
      - "./config:/var/www/html/config"
      - "./data:/var/www/html/data"
    environment:
      - REDIS_HOST=redis
      - MYSQL_PASSWORD=password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db
      - TRUSTED_PROXIES=172.33.0.0/16
      - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.domain.org
    labels:
      - traefik.enable=true
      - traefik.protocol=http
      - traefik.docker.network=traefik_proxy
      - traefik.port=80
      - traefik.http.services.nextcloud.loadbalancer.server.port=80
      - traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect
      - traefik.http.routers.nextcloud.tls=true
      - traefik.http.routers.nextcloud.entrypoints=websecure
      - traefik.http.routers.nextcloud.tls.certresolver=letsencrypt
      - traefik.http.routers.nextcloud.rule=Host(`nextcloud.domain.org`)
      - traefik.http.middlewares.nextcloud.headers.contentSecurityPolicy=frame-ancestors 'self' domain.org *.domain.org
      - traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011
      - traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true
      - traefik.http.middlewares.nextcloud.headers.stsPreload=true
      - traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav
      - traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/

raimund-schluessler avatar Feb 22 '21 21:02 raimund-schluessler

I solved this problem by assigning a static IP to the traefik proxy network and putting this IP range as trusted proxy in the nextcloud configuration:

docker-compose for traefik:

version: '3.7'

networks:
  proxy:
    name: traefik_proxy
    ipam:
      config:
        - subnet: 172.33.0.0/16

services:

  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    networks:
      - proxy
    volumes:
      - "./config/traefik.toml:/etc/traefik/traefik.toml:ro"
      - "./config/dynamic.toml:/etc/traefik/dynamic.toml:ro"
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

docker-compose for nextcloud:

version: '3.7'

networks:
  nextcloud:
  proxy:
    external:
      name: traefik_proxy

services:
  db:
    image: mariadb
    restart: always
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - "./db:/var/lib/mysql"
    networks:
      - nextcloud
    environment:
      - MYSQL_ROOT_PASSWORD=passwordpassword
      - MYSQL_PASSWORD=password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  redis:
    image: redis:latest
    restart: always
    networks:
      - nextcloud
    volumes:
      - "./redis:/var/lib/redis"

  app:
    image: nextcloud:latest
    restart: always
    networks:
      - nextcloud
      - proxy
    depends_on:
      - db
      - redis
    volumes:
      - "./nextcloud:/var/www/html"
      - "./apps:/var/www/html/custom_apps"
      - "./config:/var/www/html/config"
      - "./data:/var/www/html/data"
    environment:
      - REDIS_HOST=redis
      - MYSQL_PASSWORD=password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db
      - TRUSTED_PROXIES=172.33.0.0/16
      - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.domain.org
    labels:
      - traefik.enable=true
      - traefik.protocol=http
      - traefik.docker.network=traefik_proxy
      - traefik.port=80
      - traefik.http.services.nextcloud.loadbalancer.server.port=80
      - traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect
      - traefik.http.routers.nextcloud.tls=true
      - traefik.http.routers.nextcloud.entrypoints=websecure
      - traefik.http.routers.nextcloud.tls.certresolver=letsencrypt
      - traefik.http.routers.nextcloud.rule=Host(`nextcloud.domain.org`)
      - traefik.http.middlewares.nextcloud.headers.contentSecurityPolicy=frame-ancestors 'self' domain.org *.domain.org
      - traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011
      - traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true
      - traefik.http.middlewares.nextcloud.headers.stsPreload=true
      - traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav
      - traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/

Thanks, this worked for Nginx Proxy Manager as well

remus-selea avatar Sep 23 '22 12:09 remus-selea

Closing as this seems addressed and there's nothing actionable remaining to do here that is related to the image itself.

joshtrichards avatar Mar 25 '24 20:03 joshtrichards