mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Merge 863f719da3 into c154d23cfe
This commit is contained in:
commit
02698c3f3e
2 changed files with 35 additions and 98 deletions
|
|
@ -19,36 +19,37 @@ The following steps are require in SnappyMail:
|
|||
- Master User is dependent on Dovecot config (see below)
|
||||
- Master User Password is dependent on Dovecot config (see below)
|
||||
- Header Name is dependent on authentication solution. This is the header containing the name of currently logged in user. In case of Authelia, this is "Remote-User".
|
||||
- Check Proxy: Since this plugin partially bypasses authentication, it is important to only allow this access from well-defined hosts. It is highly recommended to activate this option!
|
||||
- When checking for reverse proxy, it is required to set the IP filter to either an IP address or a subnet.
|
||||
- Automatic Login: Automatically logs in the user of user header is present (see below)
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> This plugin trusts the configured request header as proof of identity. Anyone who can reach the SnappyMail container directly and set that header can log in as any user. You **must** ensure that SnappyMail is only reachable through your reverse proxy / SSO chain (e.g. via the docker network, a firewall, or by binding SnappyMail to a non-public interface) and that the upstream proxy strips any client-supplied value of the header before forwarding. The plugin itself does no source-IP validation — earlier versions had a `check_proxy` option, but it inspected the forwarded client IP (the end user's IP) and therefore did not actually verify that the request came from the proxy. It has been removed; gate access at the network layer instead.
|
||||
|
||||
This concludes the setup of SnappyMail.
|
||||
|
||||
### Dovecot
|
||||
|
||||
In Dovecot, you need to enable Master User.
|
||||
Enable ```!include auth-master.conf.ext``` in /etc/dovecot/conf.d/10-auth.conf.
|
||||
The file /etc/dovecot/conf.d/auth-master.conf.ext should contain:
|
||||
In Dovecot 2.3, the file /etc/dovecot/conf.d/auth-master.conf.ext should contain:
|
||||
```
|
||||
# Authentication for master users. Included from auth.conf.
|
||||
|
||||
# By adding master=yes setting inside a passdb you make the passdb a list
|
||||
# of "master users", who can log in as anyone else.
|
||||
# <doc/wiki/Authentication.MasterUsers.txt>
|
||||
|
||||
# Example master user passdb using passwd-file. You can use any passdb though.
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
master = yes
|
||||
args = /etc/dovecot/master-users
|
||||
|
||||
# Unless you're using PAM, you probably still want the destination user to
|
||||
# be looked up from passdb that it really exists. pass=yes does that.
|
||||
pass = yes
|
||||
}
|
||||
```
|
||||
|
||||
In Dovecot 2.4, the file /etc/dovecot/conf.d/auth-master.conf.ext should contain:
|
||||
```
|
||||
passdb passwd-file {
|
||||
master = yes
|
||||
passwd_file_path = /etc/dovecot/master-users
|
||||
result_success = continue
|
||||
}
|
||||
```
|
||||
|
||||
You then need to create a master user in /etc/dovecot/master-users:
|
||||
```
|
||||
admin:PASSWORD::::::allow_nets=local,172.17.0.0/16
|
||||
|
|
@ -80,3 +81,11 @@ The user is always considered logged in, as authentication is handled through re
|
|||
Auto login can be disabled in the plugin settings.
|
||||
You can also change the logout link in admin panel -> Config -> custom_logout_link to the one of your authentication system, e.g., ```https://auth.yourdomain.com/logout```.
|
||||
In this case, you can log out from your overall system via SnappyMail.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### IMAP `AUTHENTICATIONFAILED` after a container rebuild / upgrade
|
||||
|
||||
The master user/password fields are encrypted at rest using SnappyMail's `APP_SALT`. If that salt is regenerated (e.g., the data volume was reset, the container was rebuilt without persisting `_data_`, or the salt file was rotated), the values in `plugin-proxy-auth.json` can no longer be decrypted. `getDecrypted()` then silently returns `null`, an empty password is passed to IMAP, and Dovecot rejects the login with `AUTHENTICATIONFAILED`.
|
||||
|
||||
Fix: open admin panel -> Extensions -> Proxy Auth, re-enter the Master User and Master Password (and any other previously-set encrypted fields), and save. The values will be re-encrypted under the current salt.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
NAME = 'Proxy Auth',
|
||||
AUTHOR = 'Philipp',
|
||||
URL = 'https://www.mundhenk.org/',
|
||||
VERSION = '0.5',
|
||||
RELEASE = '2024-09-20',
|
||||
VERSION = '0.7',
|
||||
RELEASE = '2026-05-17',
|
||||
REQUIRED = '2.36.1',
|
||||
CATEGORY = 'Login',
|
||||
LICENSE = 'MIT',
|
||||
|
|
@ -21,26 +21,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$this->addHook('login.credentials', 'MapEmailAddress');
|
||||
}
|
||||
|
||||
/* by https://gist.github.com/tott/7684443 */
|
||||
/**
|
||||
* Check if a given ip is in a network
|
||||
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
|
||||
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
|
||||
* @return boolean true if the ip is in this range / false if not.
|
||||
*/
|
||||
private function ip_in_range( $ip, $range ) {
|
||||
if ( strpos( $range, '/' ) == false ) {
|
||||
$range .= '/32';
|
||||
}
|
||||
// $range is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $range, $netmask ) = explode( '/', $range, 2 );
|
||||
$range_decimal = ip2long( $range );
|
||||
$ip_decimal = ip2long( $ip );
|
||||
$wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
public function MapEmailAddress(string &$sEmail, string &$sImapUser, string &$sPassword, string &$sSmtpUser)
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
|
|
@ -80,42 +60,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$sMsg = "Remote User: " . $sRemoteUser;
|
||||
$oLogger->Write($sMsg, $sLevel, $sPrefix);
|
||||
|
||||
$sProxyIP = $this->Config()->getDecrypted('plugin', 'proxy_ip', '');
|
||||
$sMsg = "ProxyIP: " . $sProxyIP;
|
||||
$oLogger->Write($sMsg, $sLevel, $sPrefix);
|
||||
|
||||
$sProxyCheck = $this->Config()->getDecrypted('plugin', 'proxy_check', '');
|
||||
$sClientIPs = $this->Manager()->Actions()->Http()->GetClientIP(true);
|
||||
|
||||
/* make sure that remote user is only set by authorized proxy to avoid security risks */
|
||||
if ($sProxyCheck) {
|
||||
$sProxyRequest = false;
|
||||
$sMsg = "checking client IPs: " . $sClientIPs;
|
||||
$oLogger->Write($sMsg, $sLevel, $sPrefix);
|
||||
|
||||
$sClientIPs = explode(", ", $sClientIPs);
|
||||
if (is_array($sClientIPs)) {
|
||||
foreach ($sClientIPs as &$sIP) {
|
||||
$sMsg = "checking client IP: " . $sIP;
|
||||
$oLogger->Write($sMsg, $sLevel, $sPrefix);
|
||||
|
||||
if ($this->ip_in_range($sIP, $sProxyIP)) {
|
||||
$sProxyRequest = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sMsg = "checking client IP: " . $sClientIPs;
|
||||
$oLogger->Write($sMsg, $sLevel, $sPrefix);
|
||||
|
||||
if ($this->ip_in_range($sClientIPs, $sProxyIP)) {
|
||||
$sProxyRequest = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sProxyRequest = true;
|
||||
}
|
||||
|
||||
if ($sProxyRequest) {
|
||||
/* create master user login from remote user header and settings */
|
||||
$sEmail = $sRemoteUser . $sMasterSeparator . $sMasterUser;
|
||||
$sPassword = new \SnappyMail\SensitiveString(\trim($this->Config()->getDecrypted('plugin', 'master_password', '')));
|
||||
|
|
@ -135,10 +79,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
return true;
|
||||
}
|
||||
|
||||
\MailSo\Base\Http::Location('./');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ServiceUserHeaderSet() : bool
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
|
|
@ -188,18 +128,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
->SetDescription('Name of header containing username')
|
||||
->SetDefaultValue('Remote-User')
|
||||
->SetEncrypted(),
|
||||
\RainLoop\Plugins\Property::NewInstance('check_proxy')
|
||||
->SetLabel('Check Proxy')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
|
||||
->SetDescription('Activates check if proxy is connecting')
|
||||
->SetDefaultValue(true)
|
||||
->SetEncrypted(),
|
||||
\RainLoop\Plugins\Property::NewInstance('proxy_ip')
|
||||
->SetLabel('Proxy IPNet')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
|
||||
->SetDescription('IP or Subnet of proxy, auth header will only be accepted from this address')
|
||||
->SetDefaultValue('10.1.0.0/24')
|
||||
->SetEncrypted(),
|
||||
\RainLoop\Plugins\Property::NewInstance('auto_login')
|
||||
->SetAllowedInJs(true)
|
||||
->SetLabel('Activate automatic login')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue