Forbidden (403) CSRF verification failed - on docker 11.0.1

EDIT: I just tried a fresh install from the 11.0.1 docker using the docker-compose.yml and apache configs below (used seafile-mc:11.0.1 instead of seafile-mc:latest) and I got the same error. So the problem seems to be in v11 and not in my v10 upgrade. The v10 docker worked just fine with the same docker-compose.yml and apache config.


I already searched and looked at all threads about CSRF issues in here, particularly this one and no suggested fix worked for me.

I’m using Apache with a reverse proxy, and everything worked just fine before on the v10 docker before the upgrade.

Upgraded docker to 11.0.1: stopped containers, edited docker-compose.yml and replaced seafile-mc:latest with seafile-mc:11.0.1, did a docker-compose up -d, entered seafile docker, stopped seahub and seafile services, ran the upgrade_10_11 bash script, restarted docker.

Now I get the error in the title in all browsers (chrome, firefox, etc) when I attempt to login on the web.

NOTE: desktop client sync’ing seems to work just fine … it’s the web client that croaks.

Anyone knows of a fix?

apache conf on the host

<VirtualHost *:80>
ServerName sf.example.com
Redirect permanent / https://sf.example.com/
</VirtualHost>

<VirtualHost *:443>
# enable HTTP/2, if available
#Protocols h2 http/1.1

ServerName sf.example.com
ServerAdmin noreply@example.com

RequestHeader set X-Forwarded-Proto "https"
ProxyVia On
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://localhost:20080/
ProxyPassReverse / http://localhost:20080/

# Possible values: debug, info, notice, warn, error, crit, alert, emerg.
LogLevel notice
#LogLevel notice rewrite:trace3

ErrorLog ${APACHE_LOG_DIR}/sf-error.log
CustomLog ${APACHE_LOG_DIR}/sf-access.log combined

SSLEngine on

SSLCertificateFile /etc/ssl/acme.sh/example.com.fullchain.pem
SSLCertificateKeyFile /etc/ssl/acme.sh/example.com.key.pem

<FilesMatch "\.(cgi|shtml|phtml|php)$">
  SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
  SSLOptions +StdEnvVars
</Directory>

</VirtualHost>

docker-compose.yml

services:
  db:
    image: mariadb:10.11
    container_name: seafile-mysql
    environment:
      - MYSQL_ROOT_PASSWORD=blablabla  # Requested, set the root's password of MySQL service.
      - MYSQL_LOG_CONSOLE=true
    volumes:
      - /var/seafile/mysql/db:/var/lib/mysql  # Requested, specifies the path to MySQL data persistent store.
    networks:
      - seafile-net

  memcached:
    image: memcached:1.6.18
    container_name: seafile-memcached
    entrypoint: memcached -m 256
    networks:
      - seafile-net

  seafile:
    image: seafileltd/seafile-mc:11.0.1
    container_name: seafile
    ports:
      - 20080:80
      #- 20443:443  # If https is enabled, cancel the comment.
    extra_hosts:
      - host.docker.internal:host-gateway  # allow the docker to reach host servies by using the dns name "host.docker.internal" instead of extracting the host's IP address 172
    volumes:
      - /var/seafile/data:/shared   # Requested, specifies the path to Seafile data persistent store.
    environment:
      - DB_HOST=db
      - DB_ROOT_PASSWD=blablabla  # Requested, the value should be root's password of MySQL service.
      - TIME_ZONE=Etc/UTC  # Optional, default is UTC. Should be uncomment and set to your local time zone.
      - SEAFILE_ADMIN_EMAIL=seafile.admin@example.com # Specifies Seafile admin user, default is 'me@example.com'.
      - SEAFILE_ADMIN_PASSWORD=blablabla     # Specifies Seafile admin password, default is 'asecret'.
      - SEAFILE_SERVER_LETSENCRYPT=false   # Whether to use https or not.
      - SEAFILE_SERVER_HOSTNAME=sf.example.com  # Specifies your host name if https is enabled.
      - FORCE_HTTPS_IN_CONF=true  # if you want a reverse proxy with its own ssl cert then enable this, and SEAFILE_SERVER_LETSENCRYPT as well as port 443:443
    depends_on:
      - db
      - memcached
    networks:
      - seafile-net

networks:
  seafile-net:

seahub_settings.py in docker

# -*- coding: utf-8 -*-
DEBUG = True
SECRET_KEY = "b'blablabla'"
SERVICE_URL = "https://sf.example.com/"
CSRF_TRUSTED_ORIGINS = ['sf.example.com']  # tried without [], etc

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'seahub_db',
        'USER': 'seafile',
        'PASSWORD': 'blablabla',
        'HOST': 'db',
        'PORT': '3306',
        'OPTIONS': {'charset': 'utf8mb4'},
    }
}


CACHES = {
    'default': {
        'BACKEND': 'django_pylibmc.memcached.PyLibMCCache',
        'LOCATION': 'memcached:11211',
    },
    'locmem': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    },
}
COMPRESS_CACHE_BACKEND = 'locmem'
TIME_ZONE = 'Etc/UTC'
FILE_SERVER_ROOT = "https://sf.example.com/seafhttp"

EMAIL_USE_TLS=False
EMAIL_HOST="host.docker.internal"
#EMAIL_HOST_USER="AKIAXXXXXXXXXX"
#EMAIL_HOST_PASSWORD="xxxxxxxxxxxxxxxxxxxx"
EMAIL_PORT=25
DEFAULT_FROM_EMAIL="seafile.admin@example.com"
SERVER_EMAIL="seafile.admin@example.com"

nginx conf in docker

# -*- mode: nginx -*-
# Auto generated at 10/19/2023 17:40:03
server {
listen 80;
server_name sf.example.com;

    client_max_body_size 10m;

    location / {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 310s;
        proxy_set_header Host $host;
        proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection "";
        proxy_http_version 1.1;

        client_max_body_size 0;
        access_log      /var/log/nginx/seahub.access.log seafileformat;
        error_log       /var/log/nginx/seahub.error.log;
    }

    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        client_max_body_size 0;
        proxy_connect_timeout  36000s;
        proxy_read_timeout  36000s;
        proxy_request_buffering off;
        access_log      /var/log/nginx/seafhttp.access.log seafileformat;
        error_log       /var/log/nginx/seafhttp.error.log;
    }

    location /notification/ping {
        proxy_pass http://127.0.0.1:8083/ping;
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /notification {
        proxy_pass http://127.0.0.1:8083/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /seafdav {
        proxy_pass         http://127.0.0.1:8080;
        proxy_set_header   Host $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 $server_name;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout  1200s;
        client_max_body_size 0;

        access_log      /var/log/nginx/seafdav.access.log seafileformat;
        error_log       /var/log/nginx/seafdav.error.log;
    }

    location /media {
        root /opt/seafile/seafile-server-latest/seahub;
    }

}

Same here - I have Seafile behind the nginx proxy that adds https, but that worked before with v10.

The solution seems to be here: github - haiwen/seafile/issues/2707

2 Likes

CSRF verification failed for docker after update to seafile 11.0.0 · Issue #2707 · haiwen/seafile · GitHub

Seafile 10 is using django 3.2, while Seafile 11 is using django 4.2.

Compared to django 3.2, django 4.* has introduced a new check for the origin http header in CSRF verification (Django 4.0 release notes | Django documentation | Django). It now compares the values of the origin http header and the request host (https://github.com/django/django/blob/stable/4.2.x/django/middleware/csrf.py#L287). If they are different, an error is triggered.

In your case, you modified the port mapping, causing the origin http header to be http{s}://{ip_or_domain}:20080 and the request host remains {ip_or_domain} without the custom port. This mismatch results in a CSRF error.

For Nginx, you can change your nginx config to the following to fix this problem:

    location / {
        ...
        proxy_set_header Host $host:20080;
        ....

Thanks explaining this. For clarity for everyone, when you say “For Nginx” that would be the Nginx server inside the Docker container.

Since most people are using a custom port, please consider doing both of the following:

  • document this in the installation guide
  • generate the Nginx conf inside the container based on the ports mapping value (or add an env variable that allows setting the external port), and warn the user that changing the ports value (or the env value) requires regenerating the docker or manual editing of nginx conf.

Otherwise a lot of people will be caught up by this.

Thank you for the suggestion, we will update the documentation these days.

Having the same issue. I followed the instruction about setting the port on the Nginx, and now after log in this appears…

I’ve set the custom port (8181) to location / and location /seafdav - Am I doing something wrong and I’m missing it?

For Page unavailable error, you need to check the seahub.log files to gather further information.

I can’t seem to find the seahub.log file from my docker installation (Unraid), but I found the seahub.access.log - apparently there’s an internal error? What should I look for?

Alternatively, if there’s a place where they explain how to do the installation in unRaid, i’ll look it myself (I couldn’t find any info unfortunately, so I went somewhat blind. Just a hobbyist, so i probably did some step wrong)

10.253.0.3 [01/Dec/2023:23:40:13 -0800] “GET /accounts/login/?next=/ HTTP/1.1” 200 3977 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0” 1.054

  • 10.253.0.3 [01/Dec/2023:23:40:17 -0800] “POST /accounts/login/?next=/ HTTP/1.1” 500 285 “http://(Internal-IP):8181/accounts/login/?next=/” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0” 0.386

"Depending on your installation method, seahub.log files are typically located in either of these two directories: /seafile-install-path/logs/ or /tmp/.

Or, you can enable debug mode by adding DEBUG=True to seahub_settings.py, after you restart your Seafile, detailed error msg will be showed on the webpage.

As already mentioned above, the most convenient way to know what is really causing the issue is to enable debug mode by adding DEBUG=True to the seahub_settings.py file

In my case, 403 error was caused by incorrect CSRF_TRUSTED_ORIGINS and particularly that it missed the scheme. I.e. instead of having something like this:

CSRF_TRUSTED_ORIGINS = ['sf.example.com']

it should be

CSRF_TRUSTED_ORIGINS = ['https://sf.example.com']  # or replace https with http if running on port 80 without certificate