diff --git a/src/Command/CheckRequirementsCommand.php b/src/Command/CheckRequirementsCommand.php index f9080c42..fac928b2 100644 --- a/src/Command/CheckRequirementsCommand.php +++ b/src/Command/CheckRequirementsCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ namespace App\Command; +use App\Services\System\AppSecretChecker; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -33,7 +34,9 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; #[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')] class CheckRequirementsCommand extends Command { - public function __construct(protected ContainerBagInterface $params) + public function __construct(protected ContainerBagInterface $params, AppSecretChecker $secretChecker, + private readonly AppSecretChecker $appSecretChecker + ) { parent::__construct(); } @@ -121,6 +124,16 @@ class CheckRequirementsCommand extends Command $io->success('Debug mode disabled.'); } + //Check if APP_SECRET has been changed from the default + if ($io->isVerbose()) { + $io->comment('Checking APP_SECRET...'); + } + if ($this->appSecretChecker->isInsecureSecret()) { + $io->warning('APP_SECRET is set to the default value shipped with Part-DB. This is a security risk! Generate a new secret (e.g. using "openssl rand -hex 32") and set it as APP_SECRET in your .env.local file.'); + } elseif (!$only_issues) { + $io->success('APP_SECRET has been changed from the default value.'); + } + } protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php index 6f863a3c..f64f281f 100644 --- a/src/Controller/HomepageController.php +++ b/src/Controller/HomepageController.php @@ -24,6 +24,7 @@ namespace App\Controller; use App\DataTables\LogDataTable; use App\Entity\Parts\Part; +use App\Services\System\AppSecretChecker; use App\Services\System\BannerHelper; use App\Services\System\GitVersionInfoProvider; use App\Services\System\UpdateAvailableFacade; @@ -36,8 +37,11 @@ use Symfony\Component\Routing\Attribute\Route; class HomepageController extends AbstractController { - public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper) - { + public function __construct( + private readonly DataTableFactory $dataTable, + private readonly BannerHelper $bannerHelper, + private readonly AppSecretChecker $appSecretChecker, + ) { } @@ -84,6 +88,8 @@ class HomepageController extends AbstractController 'new_version_available' => $updateAvailableManager->isUpdateAvailable(), 'new_version' => $updateAvailableManager->getLatestVersionString(), 'new_version_url' => $updateAvailableManager->getLatestVersionUrl(), + 'insecure_app_secret' => $this->appSecretChecker->isInsecureSecret(), + 'suggested_app_secret' => $this->appSecretChecker->isInsecureSecret() ? $this->appSecretChecker->generateSecret() : null, ]); } } diff --git a/src/Services/System/AppSecretChecker.php b/src/Services/System/AppSecretChecker.php new file mode 100644 index 00000000..d5a29ba5 --- /dev/null +++ b/src/Services/System/AppSecretChecker.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\System; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +/** + * Checks whether APP_SECRET has been changed from the default value shipped with Part-DB. + */ +final readonly class AppSecretChecker +{ + /** Known default/example secrets that must not be used in production. */ + public const INSECURE_SECRETS = [ + 'a03498528f5a5fc089273ec9ae5b2849', // default in .env + '318b5d659e07a0b3f96d9b3a83b254ca', // default in .env.dev + ]; + + public function __construct( + #[Autowire('%kernel.secret%')] + private string $appSecret, + ) { + } + + /** + * @return bool True if the app secret is one of the known insecure default secrets, false otherwise. + */ + public function isInsecureSecret(): bool + { + return in_array($this->appSecret, self::INSECURE_SECRETS, true); + } + + /** + * Generates a new random app secret that can be used to replace the default insecure one. + * @return string + * @throws \Random\RandomException + */ + public function generateSecret(): string + { + //Symfony docs recommend 32 characters for the app secret, which are 16 random bytes when hex-encoded. + return bin2hex(random_bytes(16)); + } +} diff --git a/templates/homepage.html.twig b/templates/homepage.html.twig index 6e7aa360..d6b3a516 100644 --- a/templates/homepage.html.twig +++ b/templates/homepage.html.twig @@ -85,6 +85,15 @@ {% block content %} + {% if insecure_app_secret and is_granted('@system.server_infos') %} +
{% trans %}system.app_secret.insecure.message{% endtrans %}
+{% trans %}system.app_secret.insecure.suggestion{% endtrans %}
+ APP_SECRET={{ suggested_app_secret }}