diff --git a/.env b/.env index 8d5e5a54..8311abad 100644 --- a/.env +++ b/.env @@ -1,6 +1,10 @@ #### Part-DB Configuration # See https://docs.part-db.de/configuration.html for documentation of available options +# Change this to a random value to secure your installation! You can generate a random string with "openssl rand -hex 16" +# Share that value with nobody and keep it secret +APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 + ################################################################################### # Database settings ################################################################################### @@ -158,7 +162,6 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###> symfony/framework-bundle ### APP_ENV=prod -APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 APP_SHARE_DIR=var/share ###< symfony/framework-bundle ### diff --git a/VERSION b/VERSION index d8b69897..3cf561c0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.12.0 +2.12.1 diff --git a/docs/configuration.md b/docs/configuration.md index 7ba716c6..ea86f3a6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -114,10 +114,21 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...) * `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new versions, or if your server cannot connect to the internet. -* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, - particularly for securing and protecting various aspects of your application. It's a secret key that is used for - cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this - value should be handled as confidential data and not shared publicly. +* `APP_SECRET` (env only): A secret key used by Symfony for cryptographic operations — signing cookies, generating + CSRF tokens, and other security-sensitive tasks. **You must change this from the default value before exposing + Part-DB to any network.** The default value shipped with Part-DB is publicly known; leaving it in place would allow + an attacker to forge signed cookies and bypass CSRF protection. + + Generate a secure value and add it to `.env.local`: + ```bash + echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local + ``` + For Docker, pass it in the `environment` section of your `docker-compose.yaml`: + ```yaml + environment: + - APP_SECRET= + ``` + Part-DB displays a warning on the homepage (visible to administrators only) as long as the default value is in use. * `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the part image gallery * `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users. diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index ab07e010..97b1e662 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -47,7 +47,10 @@ services: - DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db # In docker env logs will be redirected to stderr - APP_ENV=docker - + # Secret key used to sign cookies and CSRF tokens. MUST be changed to a unique random value before going live! + # Generate one with: openssl rand -hex 32 + - APP_SECRET=CHANGE_ME + # Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ # folder (under .automigration-backup), so you can restore it, if the migration fails. @@ -89,7 +92,11 @@ services: 4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link configuration.md %}) for more information. -5. Inside the folder, run +5. Make sure to change the `APP_SECRET` variable to a unique random value (32 characters). You can generate one with the following command: +```bash +openssl rand -hex 32 +``` +6. Inside the folder, run ```bash docker-compose up -d @@ -100,7 +107,7 @@ services: > Otherwise Part-DB console might use the wrong configuration to execute commands. -6. Create the initial database with +7. Create the initial database with ```bash docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate @@ -108,7 +115,7 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate and watch for the password output -6. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown +8. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown before The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to @@ -121,6 +128,7 @@ If you want to use MySQL as a database, you can use the following docker-compose {: .warning } > You have to replace the values for MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD with your own passwords!! > You have to change MYSQL_PASSWORD in the database section and for the DATABASE_URL in the partdb section. +> Generate a random string for APP_SECRET. ```yaml version: '3.3' @@ -142,14 +150,19 @@ services: environment: # Replace SECRET_USER_PASSWORD with the value of MYSQL_PASSWORD from below - DATABASE_URL=mysql://partdb:SECRET_USER_PASSWORD@database:3306/partdb + + # Secret key used to sign cookies. MUST be changed to a unique random value before going live! + # Generate one with: openssl rand -hex 32 + - APP_SECRET=CHANGE_ME + # In docker env logs will be redirected to stderr - APP_ENV=docker - - # Uncomment this, if you want to use the automatic database migration feature. With this you do not have to - # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ - # folder (under .automigration-backup), so you can restore it, if the migration fails. - # This feature is currently experimental, so use it at your own risk! - # - DB_AUTOMIGRATE=true + + # Uncomment this, if you want to use the automatic database migration feature. With this you do not have to + # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ + # folder (under .automigration-backup), so you can restore it, if the migration fails. + # This feature is currently experimental, so use it at your own risk! + # - DB_AUTOMIGRATE=true # You can configure Part-DB using the webUI or environment variables # However you can add any other environment configuration you want here diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md index b3c61126..e9f500b8 100644 --- a/docs/installation/installation_guide-debian.md +++ b/docs/installation/installation_guide-debian.md @@ -136,6 +136,15 @@ cp .env .env.local In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings. A full list of configuration options can be found [here](../configuration.md). +{: .important } +> **Change `APP_SECRET` before going live.** The default value shipped with Part-DB is publicly known and must not be +> used in production — it would allow an attacker to forge signed cookies and bypass CSRF protection. +> Generate a new value and add it to your `.env.local`: +> ```bash +> echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local +> ``` +> or edit the file with a text editor and add a new value for `APP_SECRET` (you can generate a random value with `openssl rand -hex 32`). + Please check that the configured base currency matches your mainly used currency, as this can not be changed after creating price information. diff --git a/src/Command/CheckRequirementsCommand.php b/src/Command/CheckRequirementsCommand.php index f9080c42..97a2074d 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, + 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..7a4b1fcf --- /dev/null +++ b/src/Services/System/AppSecretChecker.php @@ -0,0 +1,63 @@ +. + */ + +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 + 'CHANGE_ME' //example secret used in documentation and error messages + ]; + + 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..2a191f14 100644 --- a/templates/homepage.html.twig +++ b/templates/homepage.html.twig @@ -85,6 +85,16 @@ {% block content %} + {% if insecure_app_secret and is_granted('@system.server_infos') %} + + {% endif %} + {% if is_granted('@system.show_updates') %} {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }} {% endif %} diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index f736121a..26f6d40f 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -64,7 +64,7 @@ currency.iso_code.caption - ISO Code + ISO-Code @@ -13647,5 +13647,23 @@ Buerklin-API-Authentication-Server: Aktiviert das Browser-Plugin für diese Instanz. + + + system.app_secret.insecure.title + Unsicheres APP_SECRET + + + + + system.app_secret.insecure.message + Die Umgebungsvariable <code>APP_SECRET</code> ist auf den Standardwert gesetzt, der mit Part-DB ausgeliefert wird. Dies stellt ein Sicherheitsrisiko dar! Setzen Sie sie in Ihrer Datei <code>.env.local</code> oder <code>docker-compose.yaml</code> auf einen neuen Zufallswert. + + + + + system.app_secret.insecure.suggestion + Sie können diesen zufällig generierten Wert verwenden (geben Sie ihn niemandem weiter): + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 11dcbd8e..117e529b 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -13649,5 +13649,23 @@ Buerklin-API Authentication server: When enabled users with the info provider permission can submit pages to Part-DB and retrieve them later. + + + system.app_secret.insecure.title + Insecure APP_SECRET + + + + + system.app_secret.insecure.message + The <code>APP_SECRET</code> environment variable is set to the default value shipped with Part-DB. This is a security risk! Set it to a new random value in your <code>.env.local</code> or <code>docker-compose.yaml</code> file. + + + + + system.app_secret.insecure.suggestion + You can use this randomly generated value (share it with nobody): + +