diff options
Diffstat (limited to 'core/Command/Security')
-rw-r--r-- | core/Command/Security/BruteforceAttempts.php | 65 | ||||
-rw-r--r-- | core/Command/Security/BruteforceResetAttempts.php | 45 | ||||
-rw-r--r-- | core/Command/Security/ExportCertificates.php | 35 | ||||
-rw-r--r-- | core/Command/Security/ImportCertificate.php | 48 | ||||
-rw-r--r-- | core/Command/Security/ListCertificates.php | 80 | ||||
-rw-r--r-- | core/Command/Security/RemoveCertificate.php | 40 |
6 files changed, 313 insertions, 0 deletions
diff --git a/core/Command/Security/BruteforceAttempts.php b/core/Command/Security/BruteforceAttempts.php new file mode 100644 index 00000000000..d5fa0a284fd --- /dev/null +++ b/core/Command/Security/BruteforceAttempts.php @@ -0,0 +1,65 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\Security\Bruteforce\IThrottler; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class BruteforceAttempts extends Base { + public function __construct( + protected IThrottler $throttler, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('security:bruteforce:attempts') + ->setDescription('Show bruteforce attempts status for a given IP address') + ->addArgument( + 'ipaddress', + InputArgument::REQUIRED, + 'IP address for which the attempts status is to be shown', + ) + ->addArgument( + 'action', + InputArgument::OPTIONAL, + 'Only count attempts for the given action', + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $ip = $input->getArgument('ipaddress'); + + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + $output->writeln('<error>"' . $ip . '" is not a valid IP address</error>'); + return 1; + } + + $data = [ + 'bypass-listed' => $this->throttler->isBypassListed($ip), + 'attempts' => $this->throttler->getAttempts( + $ip, + (string)$input->getArgument('action'), + ), + 'delay' => $this->throttler->getDelay( + $ip, + (string)$input->getArgument('action'), + ), + ]; + + $this->writeArrayInOutputFormat($input, $output, $data); + + return 0; + } +} diff --git a/core/Command/Security/BruteforceResetAttempts.php b/core/Command/Security/BruteforceResetAttempts.php new file mode 100644 index 00000000000..6987c0ef682 --- /dev/null +++ b/core/Command/Security/BruteforceResetAttempts.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\Security\Bruteforce\IThrottler; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class BruteforceResetAttempts extends Base { + public function __construct( + protected IThrottler $throttler, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('security:bruteforce:reset') + ->setDescription('resets bruteforce attempts for given IP address') + ->addArgument( + 'ipaddress', + InputArgument::REQUIRED, + 'IP address for which the attempts are to be reset' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $ip = $input->getArgument('ipaddress'); + + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + $output->writeln('<error>"' . $ip . '" is not a valid IP address</error>'); + return 1; + } + + $this->throttler->resetDelayForIP($ip); + return 0; + } +} diff --git a/core/Command/Security/ExportCertificates.php b/core/Command/Security/ExportCertificates.php new file mode 100644 index 00000000000..dcf34d4bce4 --- /dev/null +++ b/core/Command/Security/ExportCertificates.php @@ -0,0 +1,35 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +declare(strict_types=1); + +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificateManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ExportCertificates extends Base { + public function __construct( + protected ICertificateManager $certificateManager, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('security:certificates:export') + ->setDescription('export the certificate bundle'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $bundlePath = $this->certificateManager->getAbsoluteBundlePath(); + $bundle = file_get_contents($bundlePath); + $output->writeln($bundle); + return 0; + } +} diff --git a/core/Command/Security/ImportCertificate.php b/core/Command/Security/ImportCertificate.php new file mode 100644 index 00000000000..b23612baeb1 --- /dev/null +++ b/core/Command/Security/ImportCertificate.php @@ -0,0 +1,48 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificateManager; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ImportCertificate extends Base { + public function __construct( + protected ICertificateManager $certificateManager, + ) { + parent::__construct(); + } + + protected function configure() { + $this + ->setName('security:certificates:import') + ->setDescription('import trusted certificate in PEM format') + ->addArgument( + 'path', + InputArgument::REQUIRED, + 'path to the PEM certificate to import' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $path = $input->getArgument('path'); + + if (!file_exists($path)) { + $output->writeln('<error>Certificate not found, please provide a path accessible by the web server user</error>'); + return 1; + } + + $certData = file_get_contents($path); + $name = basename($path); + + $this->certificateManager->addCertificate($certData, $name); + return 0; + } +} diff --git a/core/Command/Security/ListCertificates.php b/core/Command/Security/ListCertificates.php new file mode 100644 index 00000000000..cf1874a09d3 --- /dev/null +++ b/core/Command/Security/ListCertificates.php @@ -0,0 +1,80 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificate; +use OCP\ICertificateManager; +use OCP\IL10N; +use OCP\L10N\IFactory as IL10NFactory; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ListCertificates extends Base { + protected IL10N $l; + + public function __construct( + protected ICertificateManager $certificateManager, + IL10NFactory $l10nFactory, + ) { + parent::__construct(); + $this->l = $l10nFactory->get('core'); + } + + protected function configure() { + $this + ->setName('security:certificates') + ->setDescription('list trusted certificates'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $outputType = $input->getOption('output'); + if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { + $certificates = array_map(function (ICertificate $certificate) { + return [ + 'name' => $certificate->getName(), + 'common_name' => $certificate->getCommonName(), + 'organization' => $certificate->getOrganization(), + 'expire' => $certificate->getExpireDate()->format(\DateTimeInterface::ATOM), + 'issuer' => $certificate->getIssuerName(), + 'issuer_organization' => $certificate->getIssuerOrganization(), + 'issue_date' => $certificate->getIssueDate()->format(\DateTimeInterface::ATOM) + ]; + }, $this->certificateManager->listCertificates()); + if ($outputType === self::OUTPUT_FORMAT_JSON) { + $output->writeln(json_encode(array_values($certificates))); + } else { + $output->writeln(json_encode(array_values($certificates), JSON_PRETTY_PRINT)); + } + } else { + $table = new Table($output); + $table->setHeaders([ + 'File Name', + 'Common Name', + 'Organization', + 'Valid Until', + 'Issued By' + ]); + + $rows = array_map(function (ICertificate $certificate) { + return [ + $certificate->getName(), + $certificate->getCommonName(), + $certificate->getOrganization(), + $this->l->l('date', $certificate->getExpireDate()), + $certificate->getIssuerName() + ]; + }, $this->certificateManager->listCertificates()); + $table->setRows($rows); + $table->render(); + } + return 0; + } +} diff --git a/core/Command/Security/RemoveCertificate.php b/core/Command/Security/RemoveCertificate.php new file mode 100644 index 00000000000..48062724d52 --- /dev/null +++ b/core/Command/Security/RemoveCertificate.php @@ -0,0 +1,40 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificateManager; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class RemoveCertificate extends Base { + public function __construct( + protected ICertificateManager $certificateManager, + ) { + parent::__construct(); + } + + protected function configure() { + $this + ->setName('security:certificates:remove') + ->setDescription('remove trusted certificate') + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'the file name of the certificate to remove' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $name = $input->getArgument('name'); + + $this->certificateManager->removeCertificate($name); + return 0; + } +} |