From dca5f1baab2ed5a0d5e8309df6b2909a15ed70a4 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 12 Apr 2023 15:32:22 +0200 Subject: [PATCH] [Web] move /process/login to internal endpoint --- .gitignore | 1 + data/Dockerfiles/dovecot/docker-entrypoint.sh | 10 ++-- data/conf/dovecot/auth/mailcowauth.php | 54 +++++++++++++++++++ data/conf/nginx/mailcow_auth.conf | 23 ++++++++ data/web/inc/functions.auth.inc.php | 27 ++++------ data/web/inc/functions.inc.php | 19 +++++++ data/web/inc/prerequisites.inc.php | 17 +----- data/web/json_api.php | 20 ------- docker-compose.yml | 8 +++ generate_config.sh | 2 +- 10 files changed, 123 insertions(+), 58 deletions(-) create mode 100644 data/conf/dovecot/auth/mailcowauth.php create mode 100644 data/conf/nginx/mailcow_auth.conf diff --git a/.gitignore b/.gitignore index e25639a70..e1e8e8882 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ rebuild-images.sh refresh_images.sh update_diffs/ create_cold_standby.sh +!data/conf/nginx/mailcow_auth.conf diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 9755398e7..ca06108dc 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -152,13 +152,14 @@ function auth_password_verify(request, password) -- check against mailbox passwds local b, c = https.request { method = "POST", - url = "https://nginx/api/v1/process/login", + url = "https://nginx:9082", source = ltn12.source.string(req_json), headers = { ["content-type"] = "application/json", ["content-length"] = tostring(#req_json) }, - sink = ltn12.sink.table(res) + sink = ltn12.sink.table(res), + insecure = true } local api_response = json.decode(table.concat(res)) if api_response.role == 'user' then @@ -182,13 +183,14 @@ function auth_password_verify(request, password) local b, c = https.request { method = "POST", - url = "https://nginx/api/v1/process/login", + url = "https://nginx:9082", source = ltn12.source.string(req_json), headers = { ["content-type"] = "application/json", ["content-length"] = tostring(#req_json) }, - sink = ltn12.sink.table(res) + sink = ltn12.sink.table(res), + insecure = true } local api_response = json.decode(table.concat(res)) if api_response.role == 'user' then diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php new file mode 100644 index 000000000..57dffdb57 --- /dev/null +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -0,0 +1,54 @@ + false, "role" => false); +if(!isset($post['username']) || !isset($post['password'])){ + echo json_encode($return); + exit(); +} + +require_once('../../../web/inc/vars.inc.php'); +if (file_exists('../../../web/inc/vars.local.inc.php')) { + include_once('../../../web/inc/vars.local.inc.php'); +} +require_once '../../../web/inc/lib/vendor/autoload.php'; + +// Do not show errors, we log to using error_log +ini_set('error_reporting', 0); +// Init database +//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name; +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; +$opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("MAILCOWAUTH: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +// Load core functions first +require_once 'functions.inc.php'; +require_once 'functions.auth.inc.php'; +require_once 'sessions.inc.php'; + +// Init Keycloak Provider +$iam_provider = identity_provider('init'); + +$result = check_login($post['username'], $post['password'], $post['protocol'], true); +if ($result) { + $return = array("success" => true, "role" => $result); +} + +echo json_encode($return); +exit(); diff --git a/data/conf/nginx/mailcow_auth.conf b/data/conf/nginx/mailcow_auth.conf new file mode 100644 index 000000000..3e4a8c736 --- /dev/null +++ b/data/conf/nginx/mailcow_auth.conf @@ -0,0 +1,23 @@ +server { + listen 9082 ssl http2; + + ssl_certificate /etc/ssl/mail/cert.pem; + ssl_certificate_key /etc/ssl/mail/key.pem; + + index mailcowauth.php; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /mailcowauth; + client_max_body_size 10M; + + location ~ \.php$ { + client_max_body_size 10M; + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass phpfpm:9001; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } +} diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 83c9ba8b1..d68a7b6e5 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -9,7 +9,7 @@ function unset_auth_session(){ unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_methods']); } -function check_login($user, $pass, $app_passwd_data = false) { +function check_login($user, $pass, $app_passwd_data = false, $is_internal = false) { global $pdo; global $redis; @@ -35,12 +35,6 @@ function check_login($user, $pass, $app_passwd_data = false) { } // Validate mailbox user - // skip log & ldelay if requests comes from dovecot - $is_dovecot = false; - $request_ip = $_SERVER['REMOTE_ADDR']; - if ($request_ip == getenv('IPV4_NETWORK').'.250'){ - $is_dovecot = true; - } // check authsource $stmt = $pdo->prepare("SELECT authsource FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain @@ -54,9 +48,9 @@ function check_login($user, $pass, $app_passwd_data = false) { // mbox does not exist, call keycloak login and create mbox if possible $identity_provider_settings = identity_provider('get'); if ($identity_provider_settings['login_flow'] == 'ropc'){ - $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot, true); + $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_internal, true); } else { - $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot, true); + $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_internal, true); } if ($result){ return $result; @@ -64,7 +58,7 @@ function check_login($user, $pass, $app_passwd_data = false) { } else if ($row['authsource'] == 'keycloak'){ if ($app_passwd_data){ // first check if password is app_password - $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_dovecot); + $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal); if ($result){ return $result; } @@ -72,9 +66,9 @@ function check_login($user, $pass, $app_passwd_data = false) { $identity_provider_settings = identity_provider('get'); if ($identity_provider_settings['login_flow'] == 'ropc'){ - $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot); + $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_internal); } else { - $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot); + $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_internal); } if ($result){ return $result; @@ -82,21 +76,20 @@ function check_login($user, $pass, $app_passwd_data = false) { } else { if ($app_passwd_data){ // first check if password is app_password - $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_dovecot); + $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal); if ($result){ return $result; } } - $result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_dovecot); + $result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_internal); if ($result){ return $result; } } - // skip log and only return false - // netfilter uses dovecot error log for banning - if ($is_dovecot){ + // skip log and only return false if it's an internal request + if ($is_internal){ return false; } if (!isset($_SESSION['ldelay'])) { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 9ee4d0d09..b88133e50 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2214,6 +2214,25 @@ function identity_provider($_action, $_data = null, $hide_secret = false) { return true; break; + case "init": + $identity_provider_settings = identity_provider('get'); + $provider = null; + if ($identity_provider_settings['server_url'] && $identity_provider_settings['realm'] && $identity_provider_settings['client_id'] && + $identity_provider_settings['client_secret'] && $identity_provider_settings['redirect_url'] && $identity_provider_settings['version']){ + $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + 'authServerUrl' => $identity_provider_settings['server_url'], + 'realm' => $identity_provider_settings['realm'], + 'clientId' => $identity_provider_settings['client_id'], + 'clientSecret' => $identity_provider_settings['client_secret'], + 'redirectUri' => $identity_provider_settings['redirect_url'], + 'version' => $identity_provider_settings['version'], + // 'encryptionAlgorithm' => 'RS256', // optional + // 'encryptionKeyPath' => '../key.pem' // optional + // 'encryptionKey' => 'contents_of_key_or_certificate' // optional + ]); + } + return $provider; + break; } } diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index d0ed31091..90cff4b22 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -179,22 +179,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; // Init Keycloak Provider -$identity_provider_settings = identity_provider('get'); -$keycloak_provider = null; -if ($identity_provider_settings['server_url'] && $identity_provider_settings['realm'] && $identity_provider_settings['client_id'] && - $identity_provider_settings['client_secret'] && $identity_provider_settings['redirect_url'] && $identity_provider_settings['version']){ - $keycloak_provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ - 'authServerUrl' => $identity_provider_settings['server_url'], - 'realm' => $identity_provider_settings['realm'], - 'clientId' => $identity_provider_settings['client_id'], - 'clientSecret' => $identity_provider_settings['client_secret'], - 'redirectUri' => $identity_provider_settings['redirect_url'], - 'version' => $identity_provider_settings['version'], - // 'encryptionAlgorithm' => 'RS256', // optional - // 'encryptionKeyPath' => '../key.pem' // optional - // 'encryptionKey' => 'contents_of_key_or_certificate' // optional - ]); -} +$keycloak_provider = identity_provider('init'); // IMAP lib // use Ddeboer\Imap\Server; diff --git a/data/web/json_api.php b/data/web/json_api.php index 9564af888..69b236d7a 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -401,26 +401,6 @@ if (isset($_GET['query'])) { ); echo json_encode($return); break; - case "login": - header('Content-Type: application/json'); - $post = trim(file_get_contents('php://input')); - if ($post) { - $post = json_decode($post, true); - } - - $return = array("success" => false, "role" => false); - if(!isset($post['username']) || !isset($post['password'])){ - echo json_encode($return); - return; - } - $result = check_login($post['username'], $post['password'], $post['protocol']); - if ($result) { - $return = array("success" => true, "role" => $result); - } - - echo json_encode($return); - return; - break; } break; case "get": diff --git a/docker-compose.yml b/docker-compose.yml index df545c15e..fffa193c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -120,6 +120,10 @@ services: - ./data/web:/web:z - ./data/conf/rspamd/dynmaps:/dynmaps:ro,z - ./data/conf/rspamd/custom/:/rspamd_custom_maps:z + - ./data/conf/dovecot/auth/mailcowauth.php:/mailcowauth/mailcowauth.php:z + - ./data/web/inc/functions.inc.php:/mailcowauth/functions.inc.php:z + - ./data/web/inc/functions.auth.inc.php:/mailcowauth/functions.auth.inc.php:z + - ./data/web/inc/sessions.inc.php:/mailcowauth/sessions.inc.php:z - rspamd-vol-1:/var/lib/rspamd - mysql-socket-vol-1:/var/run/mysqld/ - ./data/conf/sogo/:/etc/sogo/:z @@ -389,6 +393,10 @@ services: - ./data/assets/ssl/:/etc/ssl/mail/:ro,z - ./data/conf/nginx/:/etc/nginx/conf.d/:z - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z + - ./data/conf/dovecot/auth/mailcowauth.php:/mailcowauth/mailcowauth.php:z + - ./data/web/inc/functions.inc.php:/mailcowauth/functions.inc.php:z + - ./data/web/inc/functions.auth.inc.php:/mailcowauth/functions.auth.inc.php:z + - ./data/web/inc/sessions.inc.php:/mailcowauth/sessions.inc.php:z - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/ ports: - "${HTTPS_BIND:-}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" diff --git a/generate_config.sh b/generate_config.sh index 05d9ee2f1..6fdc25537 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -258,7 +258,7 @@ DBROOT=$(LC_ALL=C /dev/null | head -c 28) # Might be important: This will also change the binding within the container. # If you use a proxy within Docker, point it to the ports you set below. # Do _not_ use IP:PORT in HTTP(S)_BIND or HTTP(S)_PORT -# IMPORTANT: Do not use port 8081, 9081 or 65510! +# IMPORTANT: Do not use port 8081, 9081, 9082 or 65510! # Example: HTTP_BIND=1.2.3.4 # For IPv4 leave it as it is: HTTP_BIND= & HTTPS_PORT= # For IPv6 see https://docs.mailcow.email/post_installation/firststeps-ip_bindings/