checkup page improvements

* removed a redundant test
* more descriptive error messages
* more useful return values in failed tests
* xhr reuse where possible for faster test completion
* guard against typeerrors caused by undefined CSP
* display server token value in summary if present
pull/1/head
ansuz 3 years ago
parent 6de0030844
commit 1a18eafb7f

@ -90,6 +90,12 @@ define([
console.error(err); console.error(err);
} }
var debugOrigins = {
httpUnsafeOrigin: trimmedUnsafe,
httpSafeOrigin: trimmedSafe,
currentOrigin: window.location.origin,
};
assert(function (cb, msg) { assert(function (cb, msg) {
msg.appendChild(h('span', [ msg.appendChild(h('span', [
"CryptPad's sandbox requires that both ", "CryptPad's sandbox requires that both ",
@ -103,7 +109,7 @@ define([
])); ]));
//console.error(trimmedSafe, trimmedUnsafe); //console.error(trimmedSafe, trimmedUnsafe);
cb(Boolean(trimmedSafe && trimmedUnsafe)); cb(Boolean(trimmedSafe && trimmedUnsafe) || debugOrigins);
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
@ -119,7 +125,7 @@ define([
RESTART_WARNING(), RESTART_WARNING(),
])); ]));
return void cb(trimmedSafe !== trimmedUnsafe); return void cb(trimmedSafe !== trimmedUnsafe || trimmedUnsafe);
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
@ -132,7 +138,10 @@ define([
'. ', '. ',
RESTART_WARNING(), RESTART_WARNING(),
])); ]));
cb(trimmedSafe === ApiConfig.httpSafeOrigin && trimmedUnsafe === ApiConfig.httpUnsafeOrigin); var result = trimmedSafe === ApiConfig.httpSafeOrigin &&
trimmedUnsafe === ApiConfig.httpUnsafeOrigin;
cb(result || debugOrigins);
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
@ -149,7 +158,7 @@ define([
'.', '.',
])); ]));
var origin = window.location.origin; var origin = window.location.origin;
return void cb(ApiConfig.httpUnsafeOrigin === origin); return void cb(ApiConfig.httpUnsafeOrigin === origin || debugOrigins);
}); });
var checkAvailability = function (url, cb) { var checkAvailability = function (url, cb) {
@ -157,7 +166,7 @@ define([
url: cacheBuster(url), url: cacheBuster(url),
data: {}, data: {},
complete: function (xhr) { complete: function (xhr) {
cb(xhr.status === 200); cb(xhr.status === 200 || xhr.status);
}, },
}); });
}; };
@ -200,8 +209,8 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
to = setTimeout(function () { to = setTimeout(function () {
console.error('TIMEOUT loading iframe on the safe domain'); console.error('TIMEOUT loading iframe on the safe domain');
cb(false); cb('TIMEOUT');
}, 5000); }, 10000);
SFCommonO.initIframe(waitFor); SFCommonO.initIframe(waitFor);
}).nThen(function () { }).nThen(function () {
// Iframe is loaded // Iframe is loaded
@ -230,7 +239,7 @@ define([
console.error('Websocket TIMEOUT'); console.error('Websocket TIMEOUT');
evWSError.fire(); evWSError.fire();
cb(timeoutErr); cb(timeoutErr);
}, 5000); }, 10000);
ws.onopen = function () { ws.onopen = function () {
clearTimeout(to); clearTimeout(to);
cb(true); cb(true);
@ -389,9 +398,8 @@ define([
'cross-origin-embedder-policy': 'require-corp', 'cross-origin-embedder-policy': 'require-corp',
}; };
$.ajax(url, { Tools.common_xhr(sheetURL, function (xhr) {
complete: function (xhr) { var result = !Object.keys(expect).some(function (k) {
cb(!Object.keys(expect).some(function (k) {
var response = xhr.getResponseHeader(k); var response = xhr.getResponseHeader(k);
if (response !== expect[k]) { if (response !== expect[k]) {
msg.appendChild(h('span', [ msg.appendChild(h('span', [
@ -405,8 +413,8 @@ define([
])); ]));
return true; // returning true indicates that a value is incorrect return true; // returning true indicates that a value is incorrect
} }
})); });
}, cb(result || xhr.getAllResponseHeaders());
}); });
}); });
@ -433,14 +441,12 @@ define([
])); ]));
}; };
$.ajax('/?'+ (+new Date()), { Tools.common_xhr('/', function (xhr) {
complete: function (xhr) {
var header = xhr.getResponseHeader('permissions-policy') || ''; var header = xhr.getResponseHeader('permissions-policy') || '';
var rules = header.split(','); var rules = header.split(',');
if (rules.includes('interest-cohort=()')) { return void cb(true); } if (rules.includes('interest-cohort=()')) { return void cb(true); }
printMessage(JSON.stringify(header)); printMessage(JSON.stringify(header));
cb(header); cb(header);
},
}); });
}); });
@ -452,18 +458,14 @@ define([
"Your browser console may provide more details as to why this resource could not be loaded. ", "Your browser console may provide more details as to why this resource could not be loaded. ",
])); ]));
$.ajax(cacheBuster('/api/broadcast'), { Tools.common_xhr('/api/broadcast', function (xhr) {
dataType: 'text', var status = xhr.status;
complete: function (xhr) { cb(status === 200 || status);
cb(xhr.status === 200);
},
}); });
}); });
var checkAPIHeaders = function (url, msg, cb) { var checkAPIHeaders = function (url, msg, cb) {
$.ajax(cacheBuster(url), { Tools.common_xhr(url, function (xhr) {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders(); var allHeaders = xhr.getAllResponseHeaders();
var headers = {}; var headers = {};
var duplicated = allHeaders.split('\n').some(function (header) { var duplicated = allHeaders.split('\n').some(function (header) {
@ -487,7 +489,7 @@ define([
Object.keys(expect).forEach(function (k) { Object.keys(expect).forEach(function (k) {
var response = xhr.getResponseHeader(k); var response = xhr.getResponseHeader(k);
var expected = expect[k]; var expected = expect[k];
if (response !== expected) { if (response === expected) { return; }
incorrect = true; incorrect = true;
msg.appendChild(h('p', [ msg.appendChild(h('p', [
'The ', 'The ',
@ -500,13 +502,8 @@ define([
code(expected), code(expected),
"' as expected.", "' as expected.",
])); ]));
}
}); });
cb((!duplicated && !incorrect) || allHeaders);
if (duplicated || incorrect) { console.debug(allHeaders); }
cb(!duplicated && !incorrect);
},
}); });
}; };
@ -625,6 +622,7 @@ define([
}); });
var parseCSP = function (CSP) { var parseCSP = function (CSP) {
if (!CSP) { return {}; }
//console.error(CSP); //console.error(CSP);
var CSP_headers = {}; var CSP_headers = {};
CSP.split(";") CSP.split(";")
@ -683,7 +681,7 @@ define([
}, },
}, function (content) { }, function (content) {
var CSP_headers = parseCSP(content); var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers)); cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers);
}); });
}); });
@ -698,7 +696,7 @@ define([
}, },
}, function (content) { }, function (content) {
var CSP_headers = parseCSP(content); var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers)); cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers);
}); });
}); });
@ -817,31 +815,7 @@ define([
'. ', '. ',
RESTART_WARNING(), RESTART_WARNING(),
])); ]));
cb(isHTTPS(trimmedUnsafe) && isHTTPS(trimmedSafe)); cb(isHTTPS(trimmedUnsafe) && isHTTPS(trimmedSafe) || debugOrigins);
});
assert(function (cb, msg) { // FIXME this test has been superceded, but the descriptive text is still useful
// check that the sandbox domain is included in connect-src
msg.appendChild(h('span', [
"This instance's ",
code("Content-Security-Policy"),
" headers do not include the sandboxed domain (",
code(trimmedSafe),
") in ",
code("connect-src"),
". This can cause problems with fonts when printing office documents.",
" This is probably due to an incorrectly configured reverse proxy.",
" See the provided NGINX configuration file for an example of how to set this header correctly.",
]));
Tools.common_xhr('/', function (xhr) {
var CSP = parseCSP(xhr.getResponseHeader('content-security-policy'));
var connect = (CSP && CSP['connect-src']) || "";
if (connect.includes(trimmedSafe)) {
return void cb(true);
}
cb(CSP);
});
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
@ -904,6 +878,22 @@ define([
}); });
*/ */
var CSP_DESCRIPTIONS = {
'default-src': '',
'style-src': '',
'font-src': '',
'child-src': '',
'frame-src': '',
'script-src': '',
'connect-src': "This rule restricts which URLs can be loaded by scripts. Overly permissive settings can allow users to be tracking using external resources, while overly restrictive settings may block pages from loading entirely.",
'img-src': '',
'media-src': '',
'worker-src': '',
'manifest-src': '',
'frame-ancestors': ' This rule determines which sites can embed content from this instance in an iframe.',
};
var validateCSP = function (raw, msg, expected) { var validateCSP = function (raw, msg, expected) {
var CSP = parseCSP(raw); var CSP = parseCSP(raw);
var checkRule = function (attr, rules) { var checkRule = function (attr, rules) {
@ -917,28 +907,30 @@ define([
} }
return v.trim(); return v.trim();
}; };
if (Object.keys(expected).some(function (dir) { var failed;
Object.keys(expected).forEach(function (dir) {
var result = checkRule(dir, expected[dir]); var result = checkRule(dir, expected[dir]);
if (result) { if (!failed && result) { failed = true; }
if (!result) { return; }
msg.appendChild(h('p', [ msg.appendChild(h('p', [
'A value of ', 'A value of ',
code('"' + expected[dir].filter(Boolean).join(' ') + '"'), code('"' + expected[dir].filter(Boolean).join(' ') + '"'),
' was expected for the ', ' was expected for the ',
code(dir), code(dir),
' directive.', ' directive.',
CSP_DESCRIPTIONS[dir]
])); ]));
/*
console.log('BAD_HEADER:', { console.log('BAD_HEADER:', {
rule: dir, rule: dir,
expected: expected[dir], expected: expected[dir],
result: result, result: result,
}); });
} */
});
if (failed) { return parseCSP(raw); }
return result;
})) {
return parseCSP(raw);
}
return true; return true;
}; };
@ -966,8 +958,8 @@ define([
'default-src': ["'none'"], 'default-src': ["'none'"],
'style-src': ["'unsafe-inline'", "'self'", $outer], 'style-src': ["'unsafe-inline'", "'self'", $outer],
'font-src': ["'self'", 'data:', $outer], 'font-src': ["'self'", 'data:', $outer],
'child-src': [$outer], //["'self'", 'blob:', $outer, $sandbox], 'child-src': [$outer],
'frame-src': ["'self'", 'blob:', /*$outer, */$sandbox], 'frame-src': ["'self'", 'blob:', $sandbox],
'script-src': ["'self'", 'resource:', $outer, 'script-src': ["'self'", 'resource:', $outer,
"'unsafe-eval'", "'unsafe-eval'",
"'unsafe-inline'", "'unsafe-inline'",
@ -1009,8 +1001,8 @@ define([
'default-src': ["'none'"], 'default-src': ["'none'"],
'style-src': ["'unsafe-inline'", "'self'", $outer], 'style-src': ["'unsafe-inline'", "'self'", $outer],
'font-src': ["'self'", 'data:', $outer], 'font-src': ["'self'", 'data:', $outer],
'child-src': [$outer], //["'self'", 'blob:', $outer, $sandbox], 'child-src': [$outer],
'frame-src': ["'self'", 'blob:', /*$outer,*/ $sandbox], 'frame-src': ["'self'", 'blob:', $sandbox],
'script-src': ["'self'", 'resource:', $outer], 'script-src': ["'self'", 'resource:', $outer],
'connect-src': [ 'connect-src': [
"'self'", "'self'",
@ -1122,54 +1114,10 @@ define([
}); });
}); });
/* var serverToken;
assert(function (cb, msg) { Tools.common_xhr('/', function (xhr) {
setWarningClass(msg); serverToken = xhr.getResponseHeader('server');
$.ajax(cacheBuster('/'), {
dataType: 'text',
complete: function (xhr) {
var serverToken = xhr.getResponseHeader('server');
if (serverToken === null) { return void cb(true); }
var lowered = (serverToken || '').toLowerCase();
var family;
['Apache', 'Caddy', 'NGINX'].some(function (pattern) {
if (lowered.indexOf(pattern.toLowerCase()) !== -1) {
family = pattern;
return true;
}
});
var text = [
"This instance is set to respond with an HTTP ",
code("server"),
" header. This information can make it easier for attackers to find and exploit known vulnerabilities. ",
];
if (family === 'NGINX') { // FIXME incorrect instructions for HTTP2. needs a recompile?
msg.appendChild(h('span', text.concat([
"This can be addressed by setting ",
code("server_tokens off"),
" in your global NGINX config."
])));
return void cb(serverToken);
}
// handle other
msg.appendChild(h('span', text.concat([
"In this case, it appears that the host server is running ",
code(serverToken),
" instead of ",
code("NGINX"),
" as recommended. As such, you may not benefit from the latest security enhancements that are tested and maintained by the CryptPad development team.",
])));
cb(serverToken);
}
});
}); });
*/
var row = function (cells) { var row = function (cells) {
return h('tr', cells.map(function (cell) { return h('tr', cells.map(function (cell) {
@ -1223,6 +1171,15 @@ define([
]); ]);
}; };
var serverStatement = function (token) {
if ([null, undefined].includes(token)) { return undefined; }
return h('p.cp-notice-other', [
"Page content was served by ",
code('"' + token + '"'),
'.',
]);
};
Assert.run(function (state) { Assert.run(function (state) {
var errors = state.errors; var errors = state.errors;
var failed = errors.length; var failed = errors.length;
@ -1237,6 +1194,7 @@ define([
var summary = h('div.summary.' + statusClass, [ var summary = h('div.summary.' + statusClass, [
versionStatement(), versionStatement(),
serverStatement(serverToken),
browserStatement(), browserStatement(),
h('p', Messages._getKey('assert_numberOfTestsPassed', [ h('p', Messages._getKey('assert_numberOfTestsPassed', [
state.passed, state.passed,

Loading…
Cancel
Save