Skip to content

Commit

Permalink
Rework trusted proxies (#5549)
Browse files Browse the repository at this point in the history
* Rework trusted proxies
Fix #5502
Follow-up of #3226

New environment variable `TRUSTED_PROXY`: set to 0 to disable, or to a list of trusted IP ranges compatible with https://httpd.apache.org/docs/current/mod/mod_remoteip.html#remoteiptrustedproxy

New internal environment variable `CONN_REMOTE_ADDR` to remember the true IP address of the connection (e.g. last proxy), even when using mod_remoteip.

Current working setups should not observe any significant change.

* Minor whitespace

* Safer trusted sources during install
Rework of #5358
#5357

* Minor readme
  • Loading branch information
Alkarex committed Jul 30, 2023
1 parent 0182d84 commit e768945
Show file tree
Hide file tree
Showing 17 changed files with 100 additions and 24 deletions.
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Expand Up @@ -29,5 +29,6 @@ ENV CRON_MIN ''
ENV DATA_PATH ''
ENV FRESHRSS_ENV 'development'
ENV LISTEN '0.0.0.0:8080'
ENV TRUSTED_PROXY 0

EXPOSE 8080
1 change: 1 addition & 0 deletions Docker/Dockerfile
Expand Up @@ -58,6 +58,7 @@ ENV DATA_PATH ''
ENV FRESHRSS_ENV ''
ENV LISTEN ''
ENV OIDC_ENABLED ''
ENV TRUSTED_PROXY ''

ENTRYPOINT ["./Docker/entrypoint.sh"]

Expand Down
1 change: 1 addition & 0 deletions Docker/Dockerfile-Alpine
Expand Up @@ -54,6 +54,7 @@ ENV DATA_PATH ''
ENV FRESHRSS_ENV ''
ENV LISTEN ''
ENV OIDC_ENABLED ''
ENV TRUSTED_PROXY ''

ENTRYPOINT ["./Docker/entrypoint.sh"]

Expand Down
1 change: 1 addition & 0 deletions Docker/Dockerfile-Newest
Expand Up @@ -57,6 +57,7 @@ ENV DATA_PATH ''
ENV FRESHRSS_ENV ''
ENV LISTEN ''
ENV OIDC_ENABLED ''
ENV TRUSTED_PROXY ''

ENTRYPOINT ["./Docker/entrypoint.sh"]

Expand Down
1 change: 1 addition & 0 deletions Docker/Dockerfile-Oldest
Expand Up @@ -56,6 +56,7 @@ ENV DATA_PATH ''
ENV FRESHRSS_ENV ''
ENV LISTEN ''
ENV OIDC_ENABLED ''
ENV TRUSTED_PROXY ''

ENTRYPOINT ["./Docker/entrypoint.sh"]

Expand Down
1 change: 1 addition & 0 deletions Docker/Dockerfile-QEMU-ARM
Expand Up @@ -70,6 +70,7 @@ ENV DATA_PATH ''
ENV FRESHRSS_ENV ''
ENV LISTEN ''
ENV OIDC_ENABLED ''
ENV TRUSTED_PROXY ''

ENTRYPOINT ["./Docker/entrypoint.sh"]

Expand Down
17 changes: 12 additions & 5 deletions Docker/FreshRSS.Apache.conf
@@ -1,14 +1,21 @@
ServerName freshrss.localhost
Listen 80
DocumentRoot /var/www/FreshRSS/p/
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.1/8 172.16.0.1/12 192.168.0.1/16
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_proxy
CustomLog "|/var/www/FreshRSS/cli/sensitive-log.sh" combined_proxy
ErrorLog /dev/stderr
AllowEncodedSlashes On
ServerTokens OS
TraceEnable Off
ErrorLog /dev/stderr

# For logging the original user-agent IP instead of proxy IPs:
<IfModule mod_remoteip.c>
# Can be disabled by setting the TRUSTED_PROXY environment variable to 0:
RemoteIPHeader X-Forwarded-For
# Can be overridden by the TRUSTED_PROXY environment variable:
RemoteIPTrustedProxy 10.0.0.1/8 172.16.0.1/12 192.168.0.1/16
</IfModule>

LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_proxy
CustomLog "|/var/www/FreshRSS/cli/sensitive-log.sh" combined_proxy

<IfDefine OIDC_ENABLED>
<IfModule !auth_openidc_module>
Expand Down
7 changes: 7 additions & 0 deletions Docker/README.md
Expand Up @@ -330,6 +330,13 @@ services:
FRESHRSS_ENV: development
# Optional advanced parameter controlling the internal Apache listening port
LISTEN: 0.0.0.0:80
# Optional parameter, remove for automatic settings, set to 0 to disable,
# or (if you use a proxy) to a space-separated list of trusted IP ranges
# compatible with https://httpd.apache.org/docs/current/mod/mod_remoteip.html#remoteiptrustedproxy
# This impacts which IP address is logged (X-Forwarded-For or REMOTE_ADDR).
# This also impacts external authentication methods;
# see https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html
TRUSTED_PROXY: 172.16.0.1/12 192.168.0.1/16
# Optional parameter, set to 1 to enable OpenID Connect (only available in our Debian image)
# Requires more environment variables. See https://freshrss.github.io/FreshRSS/en/admins/16_OpenID-Connect.html
OIDC_ENABLED: 0
Expand Down
10 changes: 10 additions & 0 deletions Docker/entrypoint.sh
Expand Up @@ -11,6 +11,16 @@ if [ -n "$LISTEN" ]; then
find /etc/apache2/ -type f -name FreshRSS.Apache.conf -exec sed -r -i "\\#^Listen#s#^.*#Listen $LISTEN#" {} \;
fi

if [ -n "$TRUSTED_PROXY" ]; then
if [ "$TRUSTED_PROXY" -eq 0 ]; then
# Disable RemoteIPHeader and RemoteIPTrustedProxy
find /etc/apache2/ -type f -name FreshRSS.Apache.conf -exec sed -r -i "/^\s*RemoteIP.*$/s/^/#/" {} \;
else
# Custom list for RemoteIPTrustedProxy
find /etc/apache2/ -type f -name FreshRSS.Apache.conf -exec sed -r -i "\\#^\s*RemoteIPTrustedProxy#s#^.*#\tRemoteIPTrustedProxy $TRUSTED_PROXY#" {} \;
fi
fi

if [ -n "$OIDC_ENABLED" ] && [ "$OIDC_ENABLED" -ne 0 ]; then
a2enmod -q auth_openidc
fi
Expand Down
4 changes: 3 additions & 1 deletion Docker/freshrss/docker-compose-proxy.yml
Expand Up @@ -7,7 +7,7 @@ volumes:
services:

traefik:
image: traefik:2.6
image: traefik:2.10
container_name: traefik
restart: unless-stopped
logging:
Expand Down Expand Up @@ -42,6 +42,8 @@ services:
- traefik.enable=false

freshrss:
environment:
TRUSTED_PROXY: 172.16.0.1/12
labels:
- traefik.enable=true
- traefik.http.middlewares.freshrssM1.compress=true
Expand Down
1 change: 1 addition & 0 deletions Docker/freshrss/docker-compose.yml
Expand Up @@ -25,3 +25,4 @@ services:
environment:
TZ: Europe/Paris
CRON_MIN: '3,33'
TRUSTED_PROXY: 172.16.0.1/12 192.168.0.1/16
2 changes: 1 addition & 1 deletion app/Controllers/authController.php
Expand Up @@ -79,7 +79,7 @@ public function loginAction(): void {
'error' => [
_t('feedback.access.denied'),
' [HTTP Remote-User=' . htmlspecialchars(httpAuthUser(false), ENT_NOQUOTES, 'UTF-8') .
' ; Remote IP address=' . ($_SERVER['REMOTE_ADDR'] ?? '') . ']'
' ; Remote IP address=' . connectionRemoteAddress() . ']'
]
], false);
break;
Expand Down
11 changes: 8 additions & 3 deletions app/install.php
Expand Up @@ -208,9 +208,14 @@ function saveStep3(): bool {
return false;
}

if (FreshRSS_Context::$system_conf->auth_type === 'http_auth' && !empty($_SERVER['REMOTE_ADDR']) && is_string($_SERVER['REMOTE_ADDR'])) {
// Trust by default the remote IP address (e.g. proxy) used during install to provide remote user name
FreshRSS_Context::$system_conf->trusted_sources = [ $_SERVER['REMOTE_ADDR'] ];
if (FreshRSS_Context::$system_conf->auth_type === 'http_auth' &&
connectionRemoteAddress() !== '' &&
empty($_SERVER['REMOTE_USER']) && empty($_SERVER['REDIRECT_REMOTE_USER']) && // No safe authentication HTTP headers
(!empty($_SERVER['HTTP_REMOTE_USER']) || !empty($_SERVER['HTTP_X_WEBAUTH_USER'])) // but has unsafe authentication HTTP headers
) {
// Trust by default the remote IP address (e.g. last proxy) used during install to provide remote user name via unsafe HTTP header
FreshRSS_Context::$system_conf->trusted_sources[] = connectionRemoteAddress();
FreshRSS_Context::$system_conf->trusted_sources = array_unique(FreshRSS_Context::$system_conf->trusted_sources);
}

// Create default user files but first, we delete previous data to
Expand Down
9 changes: 6 additions & 3 deletions config.default.php
Expand Up @@ -194,9 +194,12 @@
# Disable self-update,
'disable_update' => false,

# Trusted IPs that are allowed to send unsafe headers
# Please read the documentation, before configuring this
# https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html
# Trusted IPs (e.g. of last proxy) that are allowed to send unsafe HTTP headers.
# The connection IP used during FreshRSS setup is automatically added to this list.
# Will be checked against CONN_REMOTE_ADDR (if available, to be robust even when using Apache mod_remoteip)
# or REMOTE_ADDR environment variable.
# This array can be overridden by the TRUSTED_PROXY environment variable.
# Read the documentation before configuring this https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html
'trusted_sources' => [
'127.0.0.0/8',
'::1/128',
Expand Down
6 changes: 4 additions & 2 deletions docs/en/admins/09_AccessControl.md
Expand Up @@ -24,13 +24,15 @@ variable containing the email address of the authenticated user (e.g. `REMOTE_US

## External Authentication

You may also use the `Remote-User` or `X-WebAuth-User` header to integrate with a your reverse-proxy’s authentication.
You may also use the `Remote-User` or `X-WebAuth-User` HTTP headers to integrate with a reverse-proxy’s authentication.

To enable this feature, you need to add the IP range (in CIDR notation) of your trusted proxy in the `trusted_sources` configuration option.
To allow only one IPv4, you can use a `/32` like this: `trusted_sources => [ '192.168.1.10/32' ]`.
Likewise to allow only one IPv6, you can use a `/128` like this: `trusted_sources => [ '::1/128' ]`.

WARNING: FreshRSS will trust any IP configured in the `trusted_sources` option, if your proxy isn’t properly secured, an attacker could simply attach this header and get admin access.
You may alternatively pass a `TRUSTED_PROXY` environment variable in a format compatible with [Apache’s `mod_remoteip` `RemoteIPTrustedProxy`](https://httpd.apache.org/docs/current/mod/mod_remoteip.html#remoteiptrustedproxy).

> ☠️ WARNING: FreshRSS will trust any IP configured in the `trusted_sources` option, if your proxy isn’t properly secured, an attacker could simply attach this header and get admin access.
## No Authentication

Expand Down
40 changes: 31 additions & 9 deletions lib/lib_rss.php
Expand Up @@ -653,21 +653,43 @@ function checkCIDR(string $ip, string $range): bool {
}

/**
* Check if the client is allowed to send unsafe headers
* This uses the REMOTE_ADDR header to determine the sender’s IP
* and the configuration option "trusted_sources" to get an array of the authorized ranges
*
* Use CONN_REMOTE_ADDR (if available, to be robust even when using Apache mod_remoteip) or REMOTE_ADDR environment variable to determine the connection IP.
*/
function connectionRemoteAddress(): string {
$remoteIp = $_SERVER['CONN_REMOTE_ADDR'] ?? '';
if ($remoteIp == '') {
$remoteIp = $_SERVER['REMOTE_ADDR'] ?? '';
}
if ($remoteIp == 0) {
$remoteIp = '';
}
return $remoteIp;
}

/**
* Check if the client (e.g. last proxy) is allowed to send unsafe headers.
* This uses the `TRUSTED_PROXY` environment variable or the `trusted_sources` configuration option to get an array of the authorized ranges,
* The connection IP is obtained from the `CONN_REMOTE_ADDR` (if available, to be robust even when using Apache mod_remoteip) or `REMOTE_ADDR` environment variables.
* @return bool, true if the sender’s IP is in one of the ranges defined in the configuration, else false
*/
function checkTrustedIP(): bool {
if (FreshRSS_Context::$system_conf === null) {
return false;
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
foreach (FreshRSS_Context::$system_conf->trusted_sources as $cidr) {
if (checkCIDR($_SERVER['REMOTE_ADDR'], $cidr)) {
return true;
}
$remoteIp = connectionRemoteAddress();
if ($remoteIp === '') {
return false;
}
$trusted = getenv('TRUSTED_PROXY');
if ($trusted != 0 && is_string($trusted)) {
$trusted = preg_split('/\s+/', $trusted, -1, PREG_SPLIT_NO_EMPTY);
}
if (empty($trusted)) {
$trusted = FreshRSS_Context::$system_conf->trusted_sources;
}
foreach (FreshRSS_Context::$system_conf->trusted_sources as $cidr) {
if (checkCIDR($remoteIp, $cidr)) {
return true;
}
}
return false;
Expand Down
11 changes: 11 additions & 0 deletions p/.htaccess
Expand Up @@ -35,3 +35,14 @@ AddDefaultCharset UTF-8
</FilesMatch>
Header edit Set-Cookie ^(.*)$ "$1; SameSite=Lax"
</IfModule>

# Provide the true IP address of the connection (e.g. last proxy), even when using mod_remoteip
<IfModule mod_setenvif.c>
SetEnvIfExpr "%{CONN_REMOTE_ADDR} =~ /(.*)/" CONN_REMOTE_ADDR=$1
</IfModule>
<IfModule !mod_setenvif.c>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [E=CONN_REMOTE_ADDR:%{CONN_REMOTE_ADDR}]
</IfModule>
</IfModule>

0 comments on commit e768945

Please sign in to comment.