Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117739254
D2984.1775157900.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
12 KB
Referenced Files
None
Subscribers
None
D2984.1775157900.diff
View Options
diff --git a/docker/proxy/rootfs/etc/nginx/nginx.conf b/docker/proxy/rootfs/etc/nginx/nginx.conf
--- a/docker/proxy/rootfs/etc/nginx/nginx.conf
+++ b/docker/proxy/rootfs/etc/nginx/nginx.conf
@@ -109,8 +109,9 @@
proxy_cache_bypass 1;
}
+ # FIXME do we need to whitelist certain requests that are unauthenticated?
location /Microsoft-Server-ActiveSync {
- #auth_request /auth;
+ auth_request /auth;
#auth_request_set $auth_status $upstream_status;
proxy_pass http://127.0.0.1:9080;
@@ -124,9 +125,6 @@
}
location ~* ^/\\.well-known/(caldav|carddav) {
- #auth_request /auth;
- #auth_request_set $auth_status $upstream_status;
-
proxy_pass http://127.0.0.1:9080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@@ -134,7 +132,7 @@
}
location /iRony {
- #auth_request /auth;
+ auth_request /auth;
#auth_request_set $auth_status $upstream_status;
proxy_pass http://127.0.0.1:9080;
@@ -145,7 +143,7 @@
location = /auth {
internal;
- proxy_pass http://127.0.0.1:8000/api/webhooks/nginx;
+ proxy_pass http://127.0.0.1:8000/api/webhooks/nginx-httpauth;
proxy_pass_request_body off;
proxy_set_header Host services.APP_WEBSITE_DOMAIN;
proxy_set_header Content-Length "";
diff --git a/src/app/Http/Controllers/API/V4/NGINXController.php b/src/app/Http/Controllers/API/V4/NGINXController.php
--- a/src/app/Http/Controllers/API/V4/NGINXController.php
+++ b/src/app/Http/Controllers/API/V4/NGINXController.php
@@ -10,62 +10,29 @@
class NGINXController extends Controller
{
/**
- * Authentication request.
- *
- * @todo: Separate IMAP(+STARTTLS) from IMAPS, same for SMTP/submission. =>
- * I suppose that's not necessary given that we have the information avialable in the headers?
+ * Authorize with the provided credentials.
*
- * @param \Illuminate\Http\Request $request The API request.
+ * @throws \Exception $e If the authorization fails.
*
- * @return \Illuminate\Http\Response The response
+ * @return \App\User The user
*/
- public function authenticate(Request $request)
+ private function authorizeRequest($login, $password, $clientIP)
{
- /**
- * Auth-Login-Attempt: 1
- * Auth-Method: plain
- * Auth-Pass: simple123
- * Auth-Protocol: imap
- * Auth-Ssl: on
- * Auth-User: john@kolab.org
- * Client-Ip: 127.0.0.1
- * Host: 127.0.0.1
- *
- * Auth-SSL: on
- * Auth-SSL-Verify: SUCCESS
- * Auth-SSL-Subject: /CN=example.com
- * Auth-SSL-Issuer: /CN=example.com
- * Auth-SSL-Serial: C07AD56B846B5BFF
- * Auth-SSL-Fingerprint: 29d6a80a123d13355ed16b4b04605e29cb55a5ad
- */
-
- \Log::debug("Authentication attempt");
- \Log::debug($request->headers);
-
- $login = $request->headers->get('Auth-User', null);
-
if (empty($login)) {
- return $this->byebye($request, "Empty login");
+ throw new \Exception("Empty login");
}
- // validate password, otherwise bye bye
- $password = $request->headers->get('Auth-Pass', null);
-
if (empty($password)) {
- return $this->byebye($request, "Empty password");
+ throw new \Exception("Empty password");
}
- $clientIP = $request->headers->get('Client-Ip', null);
-
if (empty($clientIP)) {
- return $this->byebye($request, "No client ip");
+ throw new \Exception("No client ip");
}
- // validate user exists, otherwise bye bye
$user = \App\User::where('email', $login)->first();
-
if (!$user) {
- return $this->byebye($request, "User not found");
+ throw new \Exception("User not found");
}
// TODO: validate the user's domain is A-OK (active, confirmed, not suspended, ldapready)
@@ -79,8 +46,7 @@
$attempt->save();
$attempt->notify();
}
- \Log::info("Failed authentication attempt due to password mismatch for user: {$login}");
- return $this->byebye($request, "Password mismatch");
+ throw new \Exception("Password mismatch");
}
// validate country of origin against restrictions, otherwise bye bye
@@ -97,7 +63,7 @@
$attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
$attempt->deny(\App\AuthAttempt::REASON_GEOLOCATION);
$attempt->notify();
- return $this->byebye($request, "Country code mismatch");
+ throw new \Exception("Country code mismatch");
}
}
@@ -108,9 +74,104 @@
if ($user->getSetting('2fa_enabled', false)) {
$authAttempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
if (!$authAttempt->waitFor2FA()) {
- return $this->byebye($request, "2fa failed");
+ throw new \Exception("2fa failed");
}
}
+ return $user;
+ }
+
+ /**
+ * Authentication request from the ngx_http_auth_request_module
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\Response The response
+ */
+ public function httpauth(Request $request)
+ {
+ /**
+ Php-Auth-Pw: simple123
+ Php-Auth-User: john@kolab.org
+ Sec-Fetch-Dest: document
+ Sec-Fetch-Mode: navigate
+ Sec-Fetch-Site: cross-site
+ Sec-Gpc: 1
+ Upgrade-Insecure-Requests: 1
+ User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:93.0) Gecko/20100101 Firefox/93.0
+ X-Forwarded-For: 31.10.153.58
+ X-Forwarded-Proto: https
+ X-Original-Uri: /iRony/
+ X-Real-Ip: 31.10.153.58
+ */
+
+ \Log::debug("Authentication attempt");
+ \Log::debug($request->headers);
+
+ try {
+ $this->authorizeRequest(
+ $request->headers->get('Php-Auth-User', null),
+ $request->headers->get('Php-Auth-Pw', null),
+ $request->headers->get('X-Real-Ip', null),
+ );
+ } catch (\Exception $e) {
+ \Log::debug("Authentication attempt failed: {$e->getMessage()}");
+ return $this->errorResponse(403);
+ }
+
+ \Log::debug("Authentication attempt succeeded");
+ $response = response("")->withHeaders(
+ [
+ "Auth-Status" => "OK"
+ ]
+ );
+ return $response;
+ }
+
+
+ /**
+ * Authentication request.
+ *
+ * @todo: Separate IMAP(+STARTTLS) from IMAPS, same for SMTP/submission. =>
+ * I suppose that's not necessary given that we have the information avialable in the headers?
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\Response The response
+ */
+ public function authenticate(Request $request)
+ {
+ /**
+ * Auth-Login-Attempt: 1
+ * Auth-Method: plain
+ * Auth-Pass: simple123
+ * Auth-Protocol: imap
+ * Auth-Ssl: on
+ * Auth-User: john@kolab.org
+ * Client-Ip: 127.0.0.1
+ * Host: 127.0.0.1
+ *
+ * Auth-SSL: on
+ * Auth-SSL-Verify: SUCCESS
+ * Auth-SSL-Subject: /CN=example.com
+ * Auth-SSL-Issuer: /CN=example.com
+ * Auth-SSL-Serial: C07AD56B846B5BFF
+ * Auth-SSL-Fingerprint: 29d6a80a123d13355ed16b4b04605e29cb55a5ad
+ */
+
+ \Log::debug("Authentication attempt");
+ \Log::debug($request->headers);
+
+ $password = $request->headers->get('Auth-Pass', null);
+
+ try {
+ $user = $this->authorizeRequest(
+ $request->headers->get('Auth-User', null),
+ $password,
+ $request->headers->get('Client-Ip', null),
+ );
+ } catch (\Exception $e) {
+ return $this->byebye($request, $e->getMessage());
+ }
// All checks passed
switch ($request->headers->get('Auth-Protocol')) {
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -146,6 +146,7 @@
],
function () {
Route::get('nginx', 'API\V4\NGINXController@authenticate');
+ Route::get('nginx-httpauth', 'API\V4\NGINXController@httpauth');
Route::post('policy/greylist', 'API\V4\PolicyController@greylist');
Route::post('policy/ratelimit', 'API\V4\PolicyController@ratelimit');
Route::post('policy/spf', 'API\V4\PolicyController@senderPolicyFramework');
diff --git a/src/tests/Feature/Controller/NGINXTest.php b/src/tests/Feature/Controller/NGINXTest.php
--- a/src/tests/Feature/Controller/NGINXTest.php
+++ b/src/tests/Feature/Controller/NGINXTest.php
@@ -163,4 +163,78 @@
$response->assertStatus(200);
$response->assertHeader('auth-status', 'OK');
}
+
+ /**
+ * Test the httpauth webhook
+ */
+ public function testNGINXHttpAuthHook(): void
+ {
+ $john = $this->getTestUser('john@kolab.org');
+
+ $response = $this->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ $pass = \App\Utils::generatePassphrase();
+ $headers = [
+ 'Php-Auth-Pw' => $pass,
+ 'Php-Auth-User' => 'john@kolab.org',
+ 'X-Forwarded-For' => '127.0.0.1',
+ 'X-Forwarded-Proto' => 'https',
+ 'X-Original-Uri' => '/iRony/',
+ 'X-Real-Ip' => '127.0.0.1',
+ ];
+
+ // Pass
+ $response = $this->withHeaders($headers)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(200);
+
+ // Invalid Password
+ $modifiedHeaders = $headers;
+ $modifiedHeaders['Php-Auth-Pw'] = "Invalid";
+ $response = $this->withHeaders($modifiedHeaders)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ // Empty Password
+ $modifiedHeaders = $headers;
+ $modifiedHeaders['Php-Auth-Pw'] = "";
+ $response = $this->withHeaders($modifiedHeaders)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ // Empty User
+ $modifiedHeaders = $headers;
+ $modifiedHeaders['Php-Auth-User'] = "";
+ $response = $this->withHeaders($modifiedHeaders)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ // Invalid User
+ $modifiedHeaders = $headers;
+ $modifiedHeaders['Php-Auth-User'] = "foo@kolab.org";
+ $response = $this->withHeaders($modifiedHeaders)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ // Empty Ip
+ $modifiedHeaders = $headers;
+ $modifiedHeaders['X-Real-Ip'] = "";
+ $response = $this->withHeaders($modifiedHeaders)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+
+ // 2-FA without device
+ $john->setSettings(
+ [
+ '2fa_enabled' => true,
+ ]
+ );
+ \App\CompanionApp::where('user_id', $john->id)->delete();
+
+ $response = $this->withHeaders($headers)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(403);
+
+ // 2-FA with accepted auth attempt
+ $authAttempt = \App\AuthAttempt::recordAuthAttempt($john, "127.0.0.1");
+ $authAttempt->accept();
+
+ $response = $this->withHeaders($headers)->get("api/webhooks/nginx-httpauth");
+ $response->assertStatus(200);
+ }
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 2, 7:25 PM (1 d, 17 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18820487
Default Alt Text
D2984.1775157900.diff (12 KB)
Attached To
Mode
D2984: Proxy authorization for irony/syncroton
Attached
Detach File
Event Timeline