CSRF Verification Failed - CSRF_TRUSTED_ORIGINS doesn’t work

Hello,
like many other people here I got trouble on upgrading seafile to version 11 with Django’s CSRF checking and I am lost…

I made a new thread to post all my configs here hoping that someone has a hint what could cause this.

The server has a custom nginx server running serving as proxy to several docker containers. One of the containers serves the original seafile docker image and it used to run well since version 10 (before the upgrade). I have anonymized my domain here in the configs, but it is a simple subdomain url.

This is the error from the containers seahub.log file:

2024-05-03 20:55:08,456 [WARNING] django.security.csrf:241 log_response Forbidden (Origin checking failed - https://file.example.com does not match any trusted origins.): /accounts/login/

Here is my main sections of the upfront nginx proxy running on my server:

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name file.example.com;

  ### ssl configuration
  ssl_protocols TLSv1.3 TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-CHACHA20-  POLY1305:EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:DHE+AESGCM:DHE:!  RSA!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED';
  ssl_ecdh_curve secp384r1;
  ssl_dhparam /etc/nginx/dh_params.pem;
  ssl_prefer_server_ciphers on;

  # SSL Stapling
  ssl_stapling on;
  ssl_stapling_verify on;

  # SSL session
  ssl_session_cache shared:SSL:1m;
  ssl_session_timeout 10m;

  # SSL certificates (RSA)
  ssl_certificate /etc/ssl/domains/example.com.full;
  ssl_certificate_key /etc/ssl/domains/example.com.key;
  # SSL certificates (ECC)
  ssl_certificate /etc/ssl/domains/example.com_ecc.full;
  ssl_certificate_key /etc/ssl/domains/example.com_ecc.key;

  ### Logging
  access_log /var/log/nginx/file.example.com.log;
  error_log /var/log/nginx/file.example.com.log;
  error_log stderr error;

  ### Nginx
  server_tokens off;
  chunked_transfer_encoding off;

  add_header "X-Frame-Options" SAMEORIGIN;
  add_header "X-Content-Type-Options" "nosniff";
  add_header "X-UA-Compatible" "IE=Edge";
  add_header "X-XSS-Protection" "1; mode=block";
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;" always;

  ### Locations / Routing

  include error_pages.conf;

  location / {
    proxy_pass         http://10.1.0.5:80;

    proxy_set_header   Host $http_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;
    proxy_read_timeout 1200s;
    # used for view/edit office file via Office Online Server
  }

  location /seafdav {
    proxy_pass         http://10.1.0.5: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_connect_timeout  36000s;
    proxy_read_timeout 36000s;
    proxy_send_timeout 36000s;
    send_timeout       36000s;
    proxy_set_header cookie $http_cookie;

    client_max_body_size 0;
    proxy_request_buffering off;

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

Here is nginx config as autogenerated in the docker container image:

# -*- mode: nginx -*-
# Auto generated at 05/03/2024 20:29:48
server {
listen 80;
server_name file.example.com;

    client_max_body_size 10m;

    location / {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 310s;
        proxy_set_header Host $http_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;
    }
}

Here is the seahub_settings.py file in the container:

# -*- coding: utf-8 -*-
SECRET_KEY = "e!$r6538@hvkk8p%q*7vrc*ahx&k2nqez3*c0=d&hr!)w901_9"

CSRF_TRUSTED_ORIGINS = ["https://file.example.com"]

FILE_SERVER_ROOT = 'https://file.example.com\seafhttp'

DEBUG = True
MEDIA_URL = '/media/'
STATIC_URL = MEDIA_URL + 'assets/'
SERVE_STATIC = False

CACHES = {
    'default': {
        'BACKEND': 'django_pylibmc.memcached.PyLibMCCache',
        'LOCATION': '10.1.0.6:11211'
    },
    'locmem': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    },
}

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

Any help or idea?

I have tried to compare your config to mine. I don’t actually know what’s wrong here, but I had a similar problem that I finally fixed. Here are a couple of differences I noticed between your config and mine:

That is the wrong slash. Try:

FILE_SERVER_ROOT = 'https://file.example.com/seafhttp'

I also have these extra lines you don’t have:

ALLOWED_HOSTS = ['.example.com']

# Whether to use a secure cookie for the CSRF cookie
# https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-cookie-secure
CSRF_COOKIE_SECURE = True

# The value of the SameSite flag on the CSRF cookie
# https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-cookie-samesite
CSRF_COOKIE_SAMESITE = 'Strict'

# https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-trusted-origins
CSRF_TRUSTED_ORIGINS = ['https://file.example.com']

Hello tomservo,
thank you for your suggestions. The slash was indeed wrong, but changing it doesn’t have an effect.
I have added your sections in the seahub_settings.py file accordingly.
Sadly this does not change something…

I am open for new suggestions or hints :slight_smile:

Sorry it took a while to get back to this.

Here is my seahub_settings.py. I have cut out about half of it because the parts for the email server, collabora server, and OAuth authentication aren’t relevant.

    # -*- coding: utf-8 -*-
    SECRET_KEY = "--redacted--"
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'seahub-db',
            'USER': 'seafile',
            'PASSWORD': '--redacted--',
            'HOST': '127.0.0.1',
            'PORT': '3306'
        }
    }
    
    FILE_SERVER_ROOT = 'https://file.example.com/seafhttp'
    
    # Some email settings cut from here
    
    SERVICE_URL = 'https://file.example.com'
    
    # Some collabora server serttings cut from here
    
    #memcached, can make the web interface more responsie
    CACHES = {
        'default': {
            'BACKEND': 'django_pylibmc.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        },
    }
    
    ### New in version 11
    # For security consideration, please set to match the host/domain of your site, e.g., ALLOWED_HOSTS = ['.example.com'].
    # Please refer https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts for details.
    ALLOWED_HOSTS = ['.example.com']
    
    # Whether to use a secure cookie for the CSRF cookie
    # https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-cookie-secure
    CSRF_COOKIE_SECURE = True
    
    # The value of the SameSite flag on the CSRF cookie
    # https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-cookie-samesite
    CSRF_COOKIE_SAMESITE = 'Strict'
    
    # https://docs.djangoproject.com/en/3.2/ref/settings/#csrf-trusted-origins
    CSRF_TRUSTED_ORIGINS = ['https://file.example.com']
    
    # Try to fix CSRF bug in new version 11 beta
    # Found the real fix, don't need this anymore
    #CSRF_TRUSTED_ORIGINS = ["https://file.example.com"]
    # DEBUG=True

And my nginx reverse proxy config for seafile.

    log_format seafileformat '$http_x_forwarded_for $remote_addr [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $upstream_response_time';
    
    server {
        listen       80;
        server_name  file.example.com;
        rewrite ^ https://$http_host$request_uri? permanent;    # Forced redirect from HTTP to HTTPS
        server_tokens off;      # Prevents the Nginx version from being displayed in the HTTP response header
    }
    
    server {
        listen 443 http2 ssl;
    
        include /etc/nginx/snippets/ssl.conf;
        include /etc/nginx/snippets/authelia-location-snippet.conf;
    
        server_name file.example.com;
        server_tokens off;
    
        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options "nosniff";
    
        location / {
            proxy_pass         http://10.10.5.5:8000;
    
            include /etc/nginx/snippets/proxy.conf;
            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://10.10.5.5:8082;
    
            include /etc/nginx/snippets/proxy.conf;
    
    	client_max_body_size 0;
    
            # supposed to fix large file uploads with web interface
            proxy_request_buffering off;
    
            access_log      /var/log/nginx/seafhttp.access.log seafileformat;
            error_log       /var/log/nginx/seafhttp.error.log;
        }
        
        
        location /notification {
            proxy_pass http://10.10.5.5:8083/;
    
            include /etc/nginx/snippets/proxy.conf;
    
            access_log      /var/log/nginx/seafile_notification.access.log;
            error_log       /var/log/nginx/seafile_notification.error.log;
        }
    }

And the /etc/nginx/snippets/proxy.conf file that you see referenced a few times above. Some of these headers are needed for seafile but are used for authelia, my OAuth provider.

        ## Headers
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-URI $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Upgrade            $http_upgrade;
        proxy_set_header Connection         "upgrade";

        ## Basic Proxy Configuration
        client_body_buffer_size 128k;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; ## Timeout if the real server is dead.
        proxy_redirect  http://  $scheme://;
        proxy_http_version 1.1;
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        proxy_buffers 64 256k;

        real_ip_header X-Forwarded-For;
        real_ip_recursive on;

        ## Advanced Proxy Configuration
        send_timeout 5m;
        proxy_read_timeout 360;
        proxy_send_timeout 360;
        proxy_connect_timeout 360;
        # End include

There was one other thing I did when troubleshooting the CSRF thing. I used wireshark to see what headers were actually being sent from the reverse proxy to the seafile server. That lead me to discover that I had defined one of the headers twice, which seemed to confuse seafile. It might be worth having a look to make sure that your proxy is really sending the headers you think it is.

ok, now I am quite frustrated. I realized, that there are two configuration file in the docker container I am using:

  • /shared/seafile/seahub_settings.py
  • /shared/seafile/conf/seahub_settings.py

It seams that the file under conf is generated an will be used afterwards. When I put my settings regarding CSRF token there it works… Thought the fault was nginx configuration…
Can someone tell me if I could safely remove the first file not under conf folder?

Thank you for your support!

Its been a while, but for the record: You can check with

lsof /shared/seafile/seahub_settings.py

when Seafile is started, if it is not used, you can delete it safely in that case.