Skip to content

Commit 225cf90

Browse files
committed
feat: Migrate HSTS check to Security headers SetupCheck
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
1 parent bc1b164 commit 225cf90

4 files changed

Lines changed: 29 additions & 198 deletions

File tree

apps/settings/lib/SetupChecks/SecurityHeaders.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,26 @@ public function run(): SetupResult {
113113
'link' => 'https://www.w3.org/TR/referrer-policy/',
114114
];
115115
}
116+
117+
$transportSecurityValidity = $response->getHeader('Strict-Transport-Security');
118+
$minimumSeconds = 15552000;
119+
if (preg_match('/^max-age=(\d+)(;.*)?$/', $transportSecurityValidity, $m)) {
120+
$transportSecurityValidity = (int)$m[1];
121+
if ($transportSecurityValidity < $minimumSeconds) {
122+
$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 enable HSTS.', [$minimumSeconds, $transportSecurityValidity])."\n";
123+
}
124+
} elseif (!empty($transportSecurityValidity)) {
125+
$msg .= $this->l10n->t('- The `Strict-Transport-Security` HTTP header is malformed: `%s`. For enhanced security, it is recommended to enable HSTS.', [$transportSecurityValidity])."\n";
126+
} else {
127+
$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";
128+
}
129+
116130
if (!empty($msg)) {
117-
return SetupResult::warning($this->l10n->t('Some headers are not set correctly on your instance')."\n".$msg, descriptionParameters:$msgParameters);
131+
return SetupResult::warning(
132+
$this->l10n->t('Some headers are not set correctly on your instance')."\n".$msg,
133+
$this->urlGenerator->linkToDocs('admin-security'),
134+
$msgParameters,
135+
);
118136
}
119137
// Skip the other requests if one works
120138
$works = true;
@@ -124,12 +142,14 @@ public function run(): SetupResult {
124142
if ($works === null) {
125143
return SetupResult::info(
126144
$this->l10n->t('Could not check that your web server serves security headers correctly. Please check manually.'),
145+
$this->urlGenerator->linkToDocs('admin-security'),
127146
);
128147
}
129148
// Otherwise if we fail we can abort here
130149
if ($works === false) {
131150
return SetupResult::warning(
132151
$this->l10n->t("Could not check that your web server serves security headers correctly, unable to query `%s`", [$url]),
152+
$this->urlGenerator->linkToDocs('admin-security'),
133153
);
134154
}
135155
}

apps/settings/src/admin.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,8 @@ window.addEventListener('DOMContentLoaded', () => {
103103
$.when(
104104
OC.SetupChecks.checkWebDAV(),
105105
OC.SetupChecks.checkSetup(),
106-
OC.SetupChecks.checkGeneric(),
107-
).then((check1, check2, check3) => {
108-
const messages = [].concat(check1, check2, check3)
106+
).then((check1, check2) => {
107+
const messages = [].concat(check1, check2)
109108
const $el = $('#postsetupchecks')
110109
$('#security-warning-state-loading').addClass('hidden')
111110

apps/settings/tests/SetupChecks/SecurityHeadersTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ public function dataSuccess(): array {
120120
'referrer-strict-origin' => [['Referrer-Policy' => 'strict-origin']],
121121
'referrer-strict-origin-when-cross-origin' => [['Referrer-Policy' => 'strict-origin-when-cross-origin']],
122122
'referrer-same-origin' => [['Referrer-Policy' => 'same-origin']],
123+
'hsts-minimum' => [['Strict-Transport-Security' => 'max-age=15552000']],
124+
'hsts-include-subdomains' => [['Strict-Transport-Security' => 'max-age=99999999; includeSubDomains']],
125+
'hsts-include-subdomains-preload' => [['Strict-Transport-Security' => 'max-age=99999999; preload; includeSubDomains']],
123126
];
124127
}
125128

@@ -161,6 +164,9 @@ public function dataFailure(): array {
161164
'referrer-origin' => [['Referrer-Policy' => 'origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
162165
'referrer-origin-when-cross-origin' => [['Referrer-Policy' => 'origin-when-cross-origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
163166
'referrer-unsafe-url' => [['Referrer-Policy' => 'unsafe-url'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
167+
'hsts-missing' => [['Strict-Transport-Security' => ''], "- The `Strict-Transport-Security` HTTP header is not set (should be at least `15552000` seconds). For enhanced security, it is recommended to enable HSTS.\n"],
168+
'hsts-too-low' => [['Strict-Transport-Security' => 'max-age=15551999'], "- The `Strict-Transport-Security` HTTP header is not set to at least `15552000` seconds (current value: `15551999`). For enhanced security, it is recommended to enable HSTS.\n"],
169+
'hsts-malformed' => [['Strict-Transport-Security' => 'iAmABogusHeader342'], "- The `Strict-Transport-Security` HTTP header is malformed: `iAmABogusHeader342`. For enhanced security, it is recommended to enable HSTS.\n"],
164170
];
165171
}
166172

core/js/tests/specs/setupchecksSpec.js

Lines changed: 0 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -320,198 +320,4 @@ describe('OC.SetupChecks tests', function() {
320320
});
321321
});
322322
});
323-
324-
describe('checkGeneric', function() {
325-
it('should return an error if the response has no statuscode 200', function(done) {
326-
var async = OC.SetupChecks.checkGeneric();
327-
328-
suite.server.requests[0].respond(
329-
500,
330-
{
331-
'Content-Type': 'application/json'
332-
}
333-
);
334-
335-
async.done(function( data, s, x ){
336-
expect(data).toEqual([{
337-
msg: 'Error occurred while checking server setup',
338-
type: OC.SetupChecks.MESSAGE_TYPE_ERROR
339-
}]);
340-
done();
341-
});
342-
});
343-
});
344-
345-
it('should return an error if the response has no statuscode 200', function(done) {
346-
var async = OC.SetupChecks.checkGeneric();
347-
348-
suite.server.requests[0].respond(
349-
500,
350-
{
351-
'Content-Type': 'application/json'
352-
},
353-
JSON.stringify({data: {serverHasInternetConnectionProblems: true}})
354-
);
355-
async.done(function( data, s, x ){
356-
expect(data).toEqual([{
357-
msg: 'Error occurred while checking server setup',
358-
type: OC.SetupChecks.MESSAGE_TYPE_ERROR
359-
}]);
360-
done();
361-
});
362-
});
363-
364-
it('should return a SSL warning if SSL used without Strict-Transport-Security-Header', function(done) {
365-
protocolStub.returns('https');
366-
var async = OC.SetupChecks.checkGeneric();
367-
368-
suite.server.requests[0].respond(200,
369-
{
370-
'X-XSS-Protection': '1; mode=block',
371-
'X-Content-Type-Options': 'nosniff',
372-
'X-Robots-Tag': 'noindex, nofollow',
373-
'X-Frame-Options': 'SAMEORIGIN',
374-
'X-Permitted-Cross-Domain-Policies': 'none',
375-
'Referrer-Policy': 'no-referrer',
376-
}
377-
);
378-
379-
async.done(function( data, s, x ){
380-
expect(data).toEqual([{
381-
msg: 'The "Strict-Transport-Security" HTTP header is not set to at least "15552000" seconds. For enhanced security, it is recommended to enable HSTS as described in the <a target="_blank" rel="noreferrer noopener" class="external" href="https://docs.example.org/admin-security">security tips ↗</a>.',
382-
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
383-
}]);
384-
done();
385-
});
386-
});
387-
388-
it('should return a SSL warning if SSL used with to small Strict-Transport-Security-Header', function(done) {
389-
protocolStub.returns('https');
390-
var async = OC.SetupChecks.checkGeneric();
391-
392-
suite.server.requests[0].respond(200,
393-
{
394-
'Strict-Transport-Security': 'max-age=15551999',
395-
'X-XSS-Protection': '1; mode=block',
396-
'X-Content-Type-Options': 'nosniff',
397-
'X-Robots-Tag': 'noindex, nofollow',
398-
'X-Frame-Options': 'SAMEORIGIN',
399-
'X-Permitted-Cross-Domain-Policies': 'none',
400-
'Referrer-Policy': 'no-referrer',
401-
}
402-
);
403-
404-
async.done(function( data, s, x ){
405-
expect(data).toEqual([{
406-
msg: 'The "Strict-Transport-Security" HTTP header is not set to at least "15552000" seconds. For enhanced security, it is recommended to enable HSTS as described in the <a target="_blank" rel="noreferrer noopener" class="external" href="https://docs.example.org/admin-security">security tips ↗</a>.',
407-
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
408-
}]);
409-
done();
410-
});
411-
});
412-
413-
it('should return a SSL warning if SSL used with to a bogus Strict-Transport-Security-Header', function(done) {
414-
protocolStub.returns('https');
415-
var async = OC.SetupChecks.checkGeneric();
416-
417-
suite.server.requests[0].respond(200,
418-
{
419-
'Strict-Transport-Security': 'iAmABogusHeader342',
420-
'X-XSS-Protection': '1; mode=block',
421-
'X-Content-Type-Options': 'nosniff',
422-
'X-Robots-Tag': 'noindex, nofollow',
423-
'X-Frame-Options': 'SAMEORIGIN',
424-
'X-Permitted-Cross-Domain-Policies': 'none',
425-
'Referrer-Policy': 'no-referrer',
426-
}
427-
);
428-
429-
async.done(function( data, s, x ){
430-
expect(data).toEqual([{
431-
msg: 'The "Strict-Transport-Security" HTTP header is not set to at least "15552000" seconds. For enhanced security, it is recommended to enable HSTS as described in the <a target="_blank" rel="noreferrer noopener" class="external" href="https://docs.example.org/admin-security">security tips ↗</a>.',
432-
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
433-
}]);
434-
done();
435-
});
436-
});
437-
438-
it('should return no SSL warning if SSL used with to exact the minimum Strict-Transport-Security-Header', function(done) {
439-
protocolStub.returns('https');
440-
var async = OC.SetupChecks.checkGeneric();
441-
442-
suite.server.requests[0].respond(200, {
443-
'Strict-Transport-Security': 'max-age=15768000',
444-
'X-XSS-Protection': '1; mode=block',
445-
'X-Content-Type-Options': 'nosniff',
446-
'X-Robots-Tag': 'noindex, nofollow',
447-
'X-Frame-Options': 'SAMEORIGIN',
448-
'X-Permitted-Cross-Domain-Policies': 'none',
449-
'Referrer-Policy': 'no-referrer',
450-
});
451-
452-
async.done(function( data, s, x ){
453-
expect(data).toEqual([]);
454-
done();
455-
});
456-
});
457-
458-
it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header', function(done) {
459-
protocolStub.returns('https');
460-
var async = OC.SetupChecks.checkGeneric();
461-
462-
suite.server.requests[0].respond(200, {
463-
'Strict-Transport-Security': 'max-age=99999999',
464-
'X-XSS-Protection': '1; mode=block',
465-
'X-Content-Type-Options': 'nosniff',
466-
'X-Robots-Tag': 'noindex, nofollow',
467-
'X-Frame-Options': 'SAMEORIGIN',
468-
'X-Permitted-Cross-Domain-Policies': 'none',
469-
'Referrer-Policy': 'no-referrer',
470-
});
471-
472-
async.done(function( data, s, x ){
473-
expect(data).toEqual([]);
474-
done();
475-
});
476-
});
477-
478-
it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header and includeSubDomains parameter', function(done) {
479-
protocolStub.returns('https');
480-
var async = OC.SetupChecks.checkGeneric();
481-
482-
suite.server.requests[0].respond(200, {
483-
'Strict-Transport-Security': 'max-age=99999999; includeSubDomains',
484-
'X-XSS-Protection': '1; mode=block',
485-
'X-Content-Type-Options': 'nosniff',
486-
'X-Robots-Tag': 'noindex, nofollow',
487-
'X-Frame-Options': 'SAMEORIGIN',
488-
'X-Permitted-Cross-Domain-Policies': 'none',
489-
'Referrer-Policy': 'no-referrer',
490-
});
491-
492-
async.done(function( data, s, x ){
493-
expect(data).toEqual([]);
494-
done();
495-
});
496-
});
497-
498-
it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header and includeSubDomains and preload parameter', function(done) {
499-
protocolStub.returns('https');
500-
var async = OC.SetupChecks.checkGeneric();
501-
502-
suite.server.requests[0].respond(200, {
503-
'Strict-Transport-Security': 'max-age=99999999; preload; includeSubDomains',
504-
'X-XSS-Protection': '1; mode=block',
505-
'X-Content-Type-Options': 'nosniff',
506-
'X-Robots-Tag': 'noindex, nofollow',
507-
'X-Frame-Options': 'SAMEORIGIN',
508-
'X-Permitted-Cross-Domain-Policies': 'none',
509-
'Referrer-Policy': 'no-referrer',
510-
});
511-
512-
async.done(function( data, s, x ){
513-
expect(data).toEqual([]);
514-
done();
515-
});
516-
});
517323
});

0 commit comments

Comments
 (0)