aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/lib/SetupChecks/SecurityHeaders.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/lib/SetupChecks/SecurityHeaders.php')
-rw-r--r--apps/settings/lib/SetupChecks/SecurityHeaders.php139
1 files changed, 139 insertions, 0 deletions
diff --git a/apps/settings/lib/SetupChecks/SecurityHeaders.php b/apps/settings/lib/SetupChecks/SecurityHeaders.php
new file mode 100644
index 00000000000..9cc6856a170
--- /dev/null
+++ b/apps/settings/lib/SetupChecks/SecurityHeaders.php
@@ -0,0 +1,139 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Settings\SetupChecks;
+
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\CheckServerResponseTrait;
+use OCP\SetupCheck\ISetupCheck;
+use OCP\SetupCheck\SetupResult;
+use Psr\Log\LoggerInterface;
+
+class SecurityHeaders implements ISetupCheck {
+
+ use CheckServerResponseTrait;
+
+ public function __construct(
+ protected IL10N $l10n,
+ protected IConfig $config,
+ protected IURLGenerator $urlGenerator,
+ protected IClientService $clientService,
+ protected LoggerInterface $logger,
+ ) {
+ }
+
+ public function getCategory(): string {
+ return 'security';
+ }
+
+ public function getName(): string {
+ return $this->l10n->t('HTTP headers');
+ }
+
+ public function run(): SetupResult {
+ $urls = [
+ ['get', $this->urlGenerator->linkToRoute('heartbeat'), [200]],
+ ];
+ $securityHeaders = [
+ 'X-Content-Type-Options' => ['nosniff', null],
+ 'X-Robots-Tag' => ['noindex,nofollow', null],
+ 'X-Frame-Options' => ['sameorigin', 'deny'],
+ 'X-Permitted-Cross-Domain-Policies' => ['none', null],
+ ];
+
+ foreach ($urls as [$verb,$url,$validStatuses]) {
+ $works = null;
+ foreach ($this->runRequest($verb, $url, ['httpErrors' => false]) as $response) {
+ // Check that the response status matches
+ if (!in_array($response->getStatusCode(), $validStatuses)) {
+ $works = false;
+ continue;
+ }
+ $msg = '';
+ $msgParameters = [];
+ foreach ($securityHeaders as $header => [$expected, $accepted]) {
+ /* Convert to lowercase and remove spaces after comas */
+ $value = preg_replace('/,\s+/', ',', strtolower($response->getHeader($header)));
+ if ($value !== $expected) {
+ if ($accepted !== null && $value === $accepted) {
+ $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. Some features might not work correctly, as it is recommended to adjust this setting accordingly.', [$header, $expected]) . "\n";
+ } else {
+ $msg .= $this->l10n->t('- The `%1$s` HTTP header is not set to `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', [$header, $expected]) . "\n";
+ }
+ }
+ }
+
+ $referrerPolicy = $response->getHeader('Referrer-Policy');
+ if (!preg_match('/(no-referrer(-when-downgrade)?|strict-origin(-when-cross-origin)?|same-origin)(,|$)/', $referrerPolicy)) {
+ $msg .= $this->l10n->t(
+ '- The `%1$s` HTTP header is not set to `%2$s`, `%3$s`, `%4$s`, `%5$s` or `%6$s`. This can leak referer information. See the {w3c-recommendation}.',
+ [
+ 'Referrer-Policy',
+ 'no-referrer',
+ 'no-referrer-when-downgrade',
+ 'strict-origin',
+ 'strict-origin-when-cross-origin',
+ 'same-origin',
+ ]
+ ) . "\n";
+ $msgParameters['w3c-recommendation'] = [
+ 'type' => 'highlight',
+ 'id' => 'w3c-recommendation',
+ 'name' => 'W3C Recommendation',
+ 'link' => 'https://www.w3.org/TR/referrer-policy/',
+ ];
+ }
+
+ $transportSecurityValidity = $response->getHeader('Strict-Transport-Security');
+ $minimumSeconds = 15552000;
+ if (preg_match('/^max-age=(\d+)(;.*)?$/', $transportSecurityValidity, $m)) {
+ $transportSecurityValidity = (int)$m[1];
+ if ($transportSecurityValidity < $minimumSeconds) {
+ $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set to at least `%d` seconds (current value: `%d`). For enhanced security, it is recommended to use a long HSTS policy.', [$minimumSeconds, $transportSecurityValidity]) . "\n";
+ }
+ } elseif (!empty($transportSecurityValidity)) {
+ $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is malformed: `%s`. For enhanced security, it is recommended to enable HSTS.', [$transportSecurityValidity]) . "\n";
+ } else {
+ $msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is not set (should be at least `%d` seconds). For enhanced security, it is recommended to enable HSTS.', [$minimumSeconds]) . "\n";
+ }
+
+ if (!empty($msg)) {
+ return SetupResult::warning(
+ $this->l10n->t('Some headers are not set correctly on your instance') . "\n" . $msg,
+ $this->urlGenerator->linkToDocs('admin-security'),
+ $msgParameters,
+ );
+ }
+ // Skip the other requests if one works
+ $works = true;
+ break;
+ }
+ // If 'works' is null then we could not connect to the server
+ if ($works === null) {
+ return SetupResult::info(
+ $this->l10n->t('Could not check that your web server serves security headers correctly. Please check manually.'),
+ $this->urlGenerator->linkToDocs('admin-security'),
+ );
+ }
+ // Otherwise if we fail we can abort here
+ if ($works === false) {
+ return SetupResult::warning(
+ $this->l10n->t('Could not check that your web server serves security headers correctly, unable to query `%s`', [$url]),
+ $this->urlGenerator->linkToDocs('admin-security'),
+ );
+ }
+ }
+ return SetupResult::success(
+ $this->l10n->t('Your server is correctly configured to send security headers.')
+ );
+ }
+}