FreshRSS / FreshRSS

A free, self-hostable news aggregator…
https://freshrss.org
GNU Affero General Public License v3.0
9.4k stars 811 forks source link

[BUG] Unable to login with http_auth (Nginx Proxy Manager/Authentik) #5300

Open zkvvoob opened 1 year ago

zkvvoob commented 1 year ago

Describe the bug A brand new installation of FreshRSS was successful - form authentication, https proxy with subdomain and SSL through Nginx Proxy Manager. Afterwards I added a Proxy Provider in Authentik and added the Nginx configuration (below). After the authentication flow, FreshRSS shows the following error message: SCR-20230415-m9m

I followed the suggestion that I found in another issue here to check what http headers are being passed on. Here's the output from the phpinfo.php file: SCR-20230415-m8e

Authentik Proxy Provider configuration (added in Nginx Proxy Manager)

# Increase buffer size for large headers
# This is needed only if you get 'upstream sent too big header while reading response
# header from upstream' error when trying to access an application protected by goauthentik
proxy_buffers 8 16k;
proxy_buffer_size 32k;

# Make sure not to redirect traffic to a port 4443
port_in_redirect off;

location / {
    # Put your proxy_pass to your application here
    proxy_pass          $forward_scheme://$server:$port;
    # Set any other headers your application might need
    # proxy_set_header Host $host;
    proxy_set_header Remote-User $authentik_username;

    ##############################
    # authentik-specific config
    ##############################
    auth_request     /outpost.goauthentik.io/auth/nginx;
    error_page       401 = @goauthentik_proxy_signin;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header       Set-Cookie $auth_cookie;

    # translate headers from the outposts back to the actual upstream
    auth_request_set $authentik_username $upstream_http_x_authentik_username;
    auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
    auth_request_set $authentik_email $upstream_http_x_authentik_email;
    auth_request_set $authentik_name $upstream_http_x_authentik_name;
    auth_request_set $authentik_uid $upstream_http_x_authentik_uid;

    proxy_set_header X-authentik-username $authentik_username;
    proxy_set_header X-authentik-groups $authentik_groups;
    proxy_set_header X-authentik-email $authentik_email;
    proxy_set_header X-authentik-name $authentik_name;
    proxy_set_header X-authentik-uid $authentik_uid;
}

# all requests to /outpost.goauthentik.io must be accessible without authentication
location /outpost.goauthentik.io {
    proxy_pass              http://10.0.0.11:9999/outpost.goauthentik.io;
    # ensure the host of this vserver matches your external URL you've configured
    # in authentik
    proxy_set_header        Host $host;
    proxy_set_header        X-Original-URL $scheme://$http_host$request_uri;
    add_header              Set-Cookie $auth_cookie;
    auth_request_set        $auth_cookie $upstream_http_set_cookie;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
}

# Special location for when the /auth endpoint returns a 401,
# redirect to the /start URL which initiates SSO
location @goauthentik_proxy_signin {
    internal;
    add_header Set-Cookie $auth_cookie;
    return 302 /outpost.goauthentik.io/start?rd=$request_uri;
    # For domain level, use the below error_page to redirect to your authentik server with the full redirect path
    # return 302 https://XXX.XXX.XXX/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
}

config.php

<?php
return array (
  'environment' => 'development',
  'salt' => 'XXXXXXXXXXXXXXXX',
  'base_url' => 'https://XXX.XXX.XXX,
  'auto_update_url' => 'https://update.freshrss.org',
  'language' => 'en',
  'title' => 'FreshRSS',
  'meta_description' => '',
  'logo_html' => '',
  'default_user' => 'myusername',
  'force_email_validation' => false,
  'allow_anonymous' => false,
  'allow_anonymous_refresh' => false,
  'auth_type' => 'http_auth',
  'http_auth_auto_register' => true,
  'http_auth_auto_register_email_field' => 'X-authentik-email',
  'api_enabled' => true,
  'unsafe_autologin_enabled' => false,
  'simplepie_syslog_enabled' => true,
  'pubsubhubbub_enabled' => true,
  'allow_robots' => false,
  'allow_referrer' => false,
  'limits' => 
  array (
    'cookie_duration' => 7776000,
    'cache_duration' => 800,
    'timeout' => 20,
    'max_inactivity' => 9223372036854775807,
    'max_feeds' => 131072,
    'max_categories' => 16384,
    'max_registrations' => 1,
  ),
  'curl_options' => 
  array (
  ),
  'db' => 
  array (
    'type' => 'sqlite',
    'host' => false,
    'user' => false,
    'password' => false,
    'base' => false,
    'prefix' => false,
    'connection_uri_params' => '',
    'pdo_options' => 
    array (
    ),
  ),
  'mailer' => 'mail',
  'smtp' => 
  array (
    'hostname' => '',
    'host' => 'localhost',
    'port' => 25,
    'auth' => false,
    'auth_type' => '',
    'username' => '',
    'password' => '',
    'secure' => '',
    'from' => 'root@localhost',
  ),
  'extensions_enabled' => 
  array (
    'Google-Groups' => true,
    'Tumblr-GDPR' => true,
  ),
  'disable_update' => true,
  'trusted_sources' => 
  array (
    0 => '127.0.0.0/8',
    1 => '10.0.0.0/8',
    2 => '10.11.0.0/24',
  ),
);

Environment information (please complete the following information):

Could you help me figure out what I'm doing wrong with the installation so that FreshRSS could work with Authentik's proxy provider?

Thank you!

math-GH commented 1 year ago

Have you checked already this documentation? https://freshrss.github.io/FreshRSS/en/admins/10_ServerConfig.html

zkvvoob commented 1 year ago

Thank you, @math-GH ! I hadn't seen this part of the admin guide.

Here's the changed configuration:

server {
    set $forward_scheme http;
    set $server         "10.0.0.11";
    set $port           1350;

    listen 80;
    listen [::]:80;

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name XXX.XXX.XXX;

    # Custom SSL
    ssl_certificate /data/custom_ssl/npm-37/fullchain.pem;
    ssl_certificate_key /data/custom_ssl/npm-37/privkey.pem;

    # Asset Caching
    include conf.d/include/assets.conf;

    # Block Exploits
    include conf.d/include/block-exploits.conf;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
    add_header Strict-Transport-Security "max-age=63072000; preload" always;

    # Force SSL
    include conf.d/include/force-ssl.conf;

    access_log /data/logs/proxy-host-61_access.log proxy;
    error_log /data/logs/proxy-host-61_error.log warn;

    root /srv/FreshRSS/p/;

    index index.php index.html index.htm;

    # php files handling
    # this regex is mandatory because of the API
    location ~ ^.+?\.php(/.*)?$ {
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        # By default, the variable PATH_INFO is not set under PHP-FPM
        # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var!
        # NOTE: the separate $path_info variable is required. For more details, see:
        # https://trac.nginx.org/nginx/ticket/321
        set $path_info $fastcgi_path_info;
        fastcgi_param PATH_INFO $path_info;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Increase buffer size for large headers
    # This is needed only if you get 'upstream sent too big header while reading response
    # header from upstream' error when trying to access an application protected by goauthentik
    proxy_buffers 8 16k;
    proxy_buffer_size 32k;

    # Make sure not to redirect traffic to a port 4443
    port_in_redirect off;

    location / {

        try_files $uri $uri/ index.php;

        # Put your proxy_pass to your application here
        proxy_pass          $forward_scheme://$server:$port;
        # Set any other headers your application might need
        # proxy_set_header Host $host;
        proxy_set_header Remote-User $authentik_username;

        ##############################
        # authentik-specific config
        ##############################
        auth_request     /outpost.goauthentik.io/auth/nginx;
        error_page       401 = @goauthentik_proxy_signin;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header       Set-Cookie $auth_cookie;

        # translate headers from the outposts back to the actual upstream
        auth_request_set $authentik_username $upstream_http_x_authentik_username;
        auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
        auth_request_set $authentik_email $upstream_http_x_authentik_email;
        auth_request_set $authentik_name $upstream_http_x_authentik_name;
        auth_request_set $authentik_uid $upstream_http_x_authentik_uid;

        proxy_set_header X-authentik-username $authentik_username;
        proxy_set_header X-authentik-groups $authentik_groups;
        proxy_set_header X-authentik-email $authentik_email;
        proxy_set_header X-authentik-name $authentik_name;
        proxy_set_header X-authentik-uid $authentik_uid;
    }

    # all requests to /outpost.goauthentik.io must be accessible without authentication
    location /outpost.goauthentik.io {
        proxy_pass              http://10.0.0.11:9999/outpost.goauthentik.io;
        # ensure the host of this vserver matches your external URL you've configured
        # in authentik
        proxy_set_header        Host $host;
        proxy_set_header        X-Original-URL $scheme://$http_host$request_uri;
        add_header              Set-Cookie $auth_cookie;
        auth_request_set        $auth_cookie $upstream_http_set_cookie;
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
    }

    # Special location for when the /auth endpoint returns a 401,
    # redirect to the /start URL which initiates SSO
    location @goauthentik_proxy_signin {
        internal;
        add_header Set-Cookie $auth_cookie;
        return 302 /outpost.goauthentik.io/start?rd=$request_uri;
        # For domain level, use the below error_page to redirect to your authentik server with the full redirect path
        # return 302 https://XXX.XXX.XXX/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
    }

    # Custom
    include /data/nginx/custom/server_proxy[.]conf;
}

Now, instead of Error 403, I get Error 502 Bad gateway. For some reason, Nginx Proxy Manager is not producing error logs, so I can't even trace the cause of the error.

Alkarex commented 1 year ago

See also http://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html#external-authentication

When you use the HTTP Basic Auth method, depending on where your authentication is done, you also need to forward the Remote-User, or a X-WebAuth-User (with the corresponding trusted_sources settings).

The Web Form authentication is easier.

zkvvoob commented 1 year ago

Hi @Alkarex,

This was my starting point in the documentation. I'm sure you can see my configuration in the initial post - I have the trusted_sources there:

'trusted_sources' => 
  array (
    0 => '127.0.0.0/8',
    1 => '10.0.0.0/8',
    2 => '10.11.0.0/24',
  )

As far as I could ascertain, both whatever header I set from Authentik - be it Remote-User or X-WebAuth-User - is passed on to FreshRSS (see the phpinfo screenshot above). What is wrong with my configuration?

The Web Form authenticatioin is without a doubt easier. However, I'd rather not make my family remember yet another set of credentials, so I'd prefer if we could leverage the existing auth solution and I'd greatly appreciate your help with that.

Thank you!

Alkarex commented 1 year ago

Quick look at your phpinfo(), I cannot see any Remote-User. Please note that HTTP_REMOTE_USER is NOT the same. The HTTP_* prefixed headers are normal headers, which any request can set (so not secured), while Remote-User has a special meaning for Web servers (CGI standard).

zkvvoob commented 1 year ago

Well, here it is: SCR-20230417-fc7

Is this not enought? Because I still cannot log in. I also tried with X-WebAuth-User, but that didn't work either.

Alkarex commented 1 year ago

Could you please put a screenshot again of the FreshRSS error, when you are actually sending the Remote-User header?

And please show again your PHP Variables section. There should be something like that: image

zkvvoob commented 1 year ago

Here you are. Are these enough? SCR-20230419-co1

SCR-20230419-cp8

Alkarex commented 1 year ago

According to your screenshots, the proxy does not pass the best / safest header, which is $_SERVER['REMOTE_USER'].

FreshRSS can accept the (less good) $_SERVER['HTTP_REMOTE_USER'] or $_SERVER['HTTP_X_WEBAUTH_USER'], but as those headers or not safe, they require the trusted IPs to be configured correctly.

The trusted IPs defined in trusted_sources are checked against the $_SERVER['REMOTE_ADDR'], which I cannot see in your screenshots. There might be a problem there.

If needed, I can make a patch to produce more debugging information.

Alkarex commented 1 year ago

You are welcome to try this patch to print $_SERVER['REMOTE_ADDR'] in case of error https://github.com/FreshRSS/FreshRSS/pull/5314

zkvvoob commented 1 year ago

Would it be too much to ask that you add an option to configure an auth header? Look at Navidrome, for instance - it accepts a Docker environment variable SCR-20230421-cpx

Calibre-web has a setting in the UI to allow external authentication and set the header: SCR-20230421-ct0

Firefly III also accepts an environment variable: SCR-20230421-cu7

All three work without any hiccups with Authentik.

If it's not too much trouble for you and doesn't go against the FreshRSS philosophy, implementing such an option would make a lot of out lives easier.

Thank you!

Alkarex commented 1 year ago

That would indeed be possible (PR welcome) - although with an increased parameter complexity and therefore more security / misconfiguration / bug risks. If there is a strong use-case, why not. But in your case though, the problem so far, as far as I can see, hasn't been so much the naming of that variable, rather than passing it correctly, right? Or do you say that Authentik is only able to pass a proprietary header?

zkvvoob commented 1 year ago

Or do you say that Authentik is only able to pass a proprietary header?

I honestly don't know. I've been communicating with users/developer on the Authentik Discord server, there's a support thread there as well. Even when I add proxy_set_header Remote-User $authentik_username in the Nginx config, the Remote-User header appears only in the HTTP Headers Information section (as you can see on the screenshot above).

Authentik supports something called Scope Mapping, which allows for passing a custom header. I've tried that as well, but either I'm nog doing it properly, or there's some disconnect along the way because with that approach the Remote-User header is nowhere to be seen on the phpinfo page. 🤷‍♂️

Alkarex commented 1 year ago

Remote-User is special and cannot just be passed as normal HTTP header. For that, could you try X-WebAuth-User?

zkvvoob commented 1 year ago

I see.

Well, proxy_set_header X-WebAuth-User $authentik_username results in this: SCR-20230421-l5z

and I still cannot login due to the same error.

Alkarex commented 1 year ago

That should do it if the IP range is also OK. Could you send a new screenshot of the error message? If possible using the current edge version of FreshRSS in order to have the patch I made for you https://github.com/FreshRSS/FreshRSS/pull/5314

zkvvoob commented 1 year ago

Here it is: the IP address it's picking is my public one. SCR-20230421-liz

My config.php contains the following:

'trusted_sources' =>
  array (
    0 => '127.0.0.0/8',
    1 => '10.0.0.0/8',
    2 => '10.11.0.0/24',
  ),

these are my internal network IP ranges. I don't think adding my public IP address is an actual solution, because I'd like to be able to access FreshRSS from anywhere. Or am I missing something?

Alkarex commented 1 year ago

It should indeed be the internal IP address. What do you have as $_SERVER['REMOTE_ADDR'] in your phpinfo? It should be your local IP there.

zkvvoob commented 1 year ago

No, REMOTE_ADDR shows my public IP address. SCR-20230421-owi

EDIT: I spun up a Firefox container in the same Docker network and then tried to access FreshRSS directly at its Docker IP address. In that instance, REMOTE_ADDR showed the Firefox container's IP.

Alkarex commented 1 year ago

Well, I believe REMOTE_ADDR is the only address visible from PHP, so that is up to your proxy. But if you whitelist that IP, you should make sure your proxy does not forward those headers if they appear from an external request.

Le ven. 21 avr. 2023 à 16:59, zkvvoob @.***> a écrit :

No, REMOTE_ADDR shows my public IP address. [image: SCR-20230421-owi] https://user-images.githubusercontent.com/7645808/233668816-e0fb1bff-7e08-4c88-878b-221063c00fa9.png

— Reply to this email directly, view it on GitHub https://github.com/FreshRSS/FreshRSS/issues/5300#issuecomment-1517963552, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAHWFRFL2LLCBEJGNEM7JPTXCKOFLANCNFSM6AAAAAAW7NC6CY . You are receiving this because you were mentioned.Message ID: @.***>