|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace App\Auth;
|
|
namespace App\Auth;
|
|
|
|
|
|
|
|
|
|
+use App\Logging\LogIdentifier;
|
|
|
use Psr\Log\LoggerInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -81,12 +82,20 @@ final class LoginThrottle
|
|
|
/** @var list<array{level: string, message: string, context: array<string, mixed>}> $logEvents */
|
|
/** @var list<array{level: string, message: string, context: array<string, mixed>}> $logEvents */
|
|
|
$logEvents = [];
|
|
$logEvents = [];
|
|
|
|
|
|
|
|
|
|
+ // SEC_REVIEW F34: log non-reversible fingerprints of the attempted
|
|
|
|
|
+ // username and source IP rather than the raw values. Triage by
|
|
|
|
|
+ // counting hits on a single fingerprint still works; a SIEM reader
|
|
|
|
|
+ // no longer sees passwords typed in the username field or raw
|
|
|
|
|
+ // client addresses.
|
|
|
|
|
+ $usernameFp = LogIdentifier::fingerprint($username);
|
|
|
|
|
+ $sourceIpFp = LogIdentifier::fingerprint($ip);
|
|
|
|
|
+
|
|
|
$this->store->mutate(static function (array $state) use (
|
|
$this->store->mutate(static function (array $state) use (
|
|
|
$now,
|
|
$now,
|
|
|
$ipKey,
|
|
$ipKey,
|
|
|
$userKey,
|
|
$userKey,
|
|
|
- $username,
|
|
|
|
|
- $ip,
|
|
|
|
|
|
|
+ $usernameFp,
|
|
|
|
|
+ $sourceIpFp,
|
|
|
&$logEvents,
|
|
&$logEvents,
|
|
|
): array {
|
|
): array {
|
|
|
$ipEntry = $state['ip'][$ipKey] ?? ['count' => 0, 'lockedUntil' => 0];
|
|
$ipEntry = $state['ip'][$ipKey] ?? ['count' => 0, 'lockedUntil' => 0];
|
|
@@ -99,8 +108,8 @@ final class LoginThrottle
|
|
|
'message' => 'local login lockout triggered',
|
|
'message' => 'local login lockout triggered',
|
|
|
'context' => [
|
|
'context' => [
|
|
|
'bucket' => 'ip',
|
|
'bucket' => 'ip',
|
|
|
- 'username' => $username,
|
|
|
|
|
- 'source_ip' => $ip,
|
|
|
|
|
|
|
+ 'username_fp' => $usernameFp,
|
|
|
|
|
+ 'source_ip_fp' => $sourceIpFp,
|
|
|
'failure_count' => $ipEntry['count'],
|
|
'failure_count' => $ipEntry['count'],
|
|
|
'lock_seconds' => $ipLock,
|
|
'lock_seconds' => $ipLock,
|
|
|
],
|
|
],
|
|
@@ -111,8 +120,8 @@ final class LoginThrottle
|
|
|
'message' => 'local login failure',
|
|
'message' => 'local login failure',
|
|
|
'context' => [
|
|
'context' => [
|
|
|
'bucket' => 'ip',
|
|
'bucket' => 'ip',
|
|
|
- 'username' => $username,
|
|
|
|
|
- 'source_ip' => $ip,
|
|
|
|
|
|
|
+ 'username_fp' => $usernameFp,
|
|
|
|
|
+ 'source_ip_fp' => $sourceIpFp,
|
|
|
'failure_count' => $ipEntry['count'],
|
|
'failure_count' => $ipEntry['count'],
|
|
|
],
|
|
],
|
|
|
];
|
|
];
|
|
@@ -129,8 +138,8 @@ final class LoginThrottle
|
|
|
'message' => 'local login lockout triggered',
|
|
'message' => 'local login lockout triggered',
|
|
|
'context' => [
|
|
'context' => [
|
|
|
'bucket' => 'username',
|
|
'bucket' => 'username',
|
|
|
- 'username' => $username,
|
|
|
|
|
- 'source_ip' => $ip,
|
|
|
|
|
|
|
+ 'username_fp' => $usernameFp,
|
|
|
|
|
+ 'source_ip_fp' => $sourceIpFp,
|
|
|
'failure_count' => $userEntry['count'],
|
|
'failure_count' => $userEntry['count'],
|
|
|
'lock_seconds' => $userLock,
|
|
'lock_seconds' => $userLock,
|
|
|
],
|
|
],
|