Improve security and PubKey handling for SFTP users.
This commit is contained in:
parent
f5c73fb108
commit
6f1402c65f
|
@ -66,6 +66,7 @@
|
|||
"php-di/php-di": "^6.0",
|
||||
"php-di/slim-bridge": "^3.0",
|
||||
"phpmyadmin/motranslator": "^5.3",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"psr/http-factory": ">1",
|
||||
"psr/simple-cache": ">1",
|
||||
"ramsey/uuid": "^4.0",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4b8224fea5f9405d7270b1719c1dc791",
|
||||
"content-hash": "ee39dba284c5c3f58875350e539c3826",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
|
|
@ -8,14 +8,13 @@ use App\Console\Command\CommandAbstract;
|
|||
use App\Entity\SftpUser;
|
||||
use Brick\Math\BigInteger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use const JSON_NUMERIC_CHECK;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const JSON_UNESCAPED_SLASHES;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'azuracast:internal:sftp-auth',
|
||||
|
@ -25,6 +24,7 @@ class SftpAuthCommand extends CommandAbstract
|
|||
{
|
||||
public function __construct(
|
||||
protected EntityManagerInterface $em,
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -32,39 +32,80 @@ class SftpAuthCommand extends CommandAbstract
|
|||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$errorResponse = json_encode(['username' => ''], JSON_THROW_ON_ERROR);
|
||||
|
||||
$username = getenv('SFTPGO_AUTHD_USERNAME') ?: null;
|
||||
$password = getenv('SFTPGO_AUTHD_PASSWORD') ?: null;
|
||||
$pubKey = getenv('SFTPGO_AUTHD_PUBLIC_KEY') ?: null;
|
||||
$username = getenv('SFTPGO_AUTHD_USERNAME') ?: '';
|
||||
$password = getenv('SFTPGO_AUTHD_PASSWORD') ?: '';
|
||||
$pubKey = getenv('SFTPGO_AUTHD_PUBLIC_KEY') ?: '';
|
||||
|
||||
$sftpUser = $this->em->getRepository(SftpUser::class)->findOneBy(['username' => $username]);
|
||||
|
||||
if ($sftpUser instanceof SftpUser && $sftpUser->authenticate($password, $pubKey)) {
|
||||
$storageLocation = $sftpUser->getStation()->getMediaStorageLocation();
|
||||
|
||||
$quotaRaw = $storageLocation->getStorageQuotaBytes();
|
||||
$quota = ($quotaRaw instanceof BigInteger)
|
||||
? (string)$quotaRaw
|
||||
: 0;
|
||||
|
||||
$row = [
|
||||
'status' => 1,
|
||||
'username' => $sftpUser->getUsername(),
|
||||
'expiration_date' => 0,
|
||||
'home_dir' => $storageLocation->getPath(),
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'quota_size' => $quota,
|
||||
'permissions' => [
|
||||
'/' => ['*'],
|
||||
],
|
||||
];
|
||||
|
||||
$io->write(json_encode($row, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK));
|
||||
return 0;
|
||||
if (empty($username)) {
|
||||
$io->write($errorResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
$io->write(json_encode(['username' => ''], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES));
|
||||
return 1;
|
||||
$sftpUser = $this->em->getRepository(SftpUser::class)->findOneBy(['username' => $username]);
|
||||
if (!($sftpUser instanceof SftpUser)) {
|
||||
$this->logger->notice(
|
||||
sprintf(
|
||||
'SFTP user "%s" not found.',
|
||||
$username
|
||||
)
|
||||
);
|
||||
|
||||
$io->write($errorResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!$sftpUser->authenticate($password, $pubKey)) {
|
||||
$this->logger->notice(
|
||||
sprintf(
|
||||
'SFTP user "%s" could not authenticate.',
|
||||
$username
|
||||
),
|
||||
[
|
||||
'hasPassword' => !empty($password),
|
||||
'hasPubKey' => !empty($pubKey),
|
||||
]
|
||||
);
|
||||
|
||||
$io->write($errorResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
$storageLocation = $sftpUser->getStation()->getMediaStorageLocation();
|
||||
|
||||
if (!$storageLocation->isLocal()) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'SFTP login failed for user "%s": Storage Location %s is not local.',
|
||||
$username,
|
||||
$storageLocation
|
||||
)
|
||||
);
|
||||
|
||||
$io->write($errorResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
$quotaRaw = $storageLocation->getStorageQuotaBytes();
|
||||
$quota = ($quotaRaw instanceof BigInteger)
|
||||
? (string)$quotaRaw
|
||||
: 0;
|
||||
|
||||
$row = [
|
||||
'status' => 1,
|
||||
'username' => $sftpUser->getUsername(),
|
||||
'expiration_date' => 0,
|
||||
'home_dir' => $storageLocation->getPath(),
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'quota_size' => $quota,
|
||||
'permissions' => [
|
||||
'/' => ['*'],
|
||||
],
|
||||
];
|
||||
|
||||
$io->write(json_encode($row, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use App\Entity\Interfaces\IdentifiableEntityInterface;
|
|||
use App\Validator\Constraints\UniqueEntity;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use OpenApi\Attributes as OA;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
use const PASSWORD_ARGON2ID;
|
||||
|
@ -101,7 +102,9 @@ class SftpUser implements IdentifiableEntityInterface
|
|||
|
||||
$pubKeysRaw = trim($this->publicKeys);
|
||||
if (!empty($pubKeysRaw)) {
|
||||
return array_filter(array_map('trim', explode("\n", $pubKeysRaw)));
|
||||
return array_filter(
|
||||
array_map([$this, 'cleanPublicKey'], explode("\n", $pubKeysRaw))
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
@ -120,9 +123,19 @@ class SftpUser implements IdentifiableEntityInterface
|
|||
|
||||
if (!empty($pubKey)) {
|
||||
$pubKeys = $this->getPublicKeysArray();
|
||||
return in_array($pubKey, $pubKeys, true);
|
||||
return in_array($this->cleanPublicKey($pubKey), $pubKeys, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function cleanPublicKey(string $pubKeyRaw): ?string
|
||||
{
|
||||
try {
|
||||
$pkObj = PublicKeyLoader::loadPublicKey(trim($pubKeyRaw));
|
||||
return trim($pkObj->toString('OpenSSH', ['comment' => '']));
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue