support modifying CSP headers at runtime

pull/1/head
ansuz 3 years ago
parent 52529f1a65
commit b40c81d088

@ -1,7 +1,8 @@
var Default = module.exports; var Default = module.exports;
Default.commonCSP = function (domain, sandbox) { Default.commonCSP = function (Env) {
domain = ' ' + domain; domain = ' ' + Env.httpUnsafeOrigin;
sandbox = Env.httpSafeOrigin;
sandbox = (sandbox && sandbox !== domain? ' ' + sandbox: ''); sandbox = (sandbox && sandbox !== domain? ' ' + sandbox: '');
// Content-Security-Policy // Content-Security-Policy
@ -31,25 +32,25 @@ Default.commonCSP = function (domain, sandbox) {
"media-src blob:", "media-src blob:",
// for accounts.cryptpad.fr authentication and cross-domain iframe sandbox // for accounts.cryptpad.fr authentication and cross-domain iframe sandbox
"frame-ancestors *", Env.disableEmbedding? `frame-ancestors ${domain}${sandbox}`: "frame-ancestors *",
"worker-src 'self'", "worker-src 'self'",
"" ""
]; ];
}; };
Default.contentSecurity = function (domain, sandbox) { Default.contentSecurity = function (Env) {
return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' '); return (Default.commonCSP(Env).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' ');
}; };
Default.padContentSecurity = function (domain, sandbox) { Default.padContentSecurity = function (Env) {
return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' '); return (Default.commonCSP(Env).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' ');
}; };
Default.httpHeaders = function () { Default.httpHeaders = function (Env) {
return { return {
"X-XSS-Protection": "1; mode=block", "X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff", "X-Content-Type-Options": "nosniff",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": Env.disableEmbedding? '': "*",
"Permissions-policy":"interest-cohort=()" "Permissions-policy":"interest-cohort=()"
}; };
}; };

@ -39,9 +39,19 @@ module.exports.create = function (config) {
DEV_MODE: false, DEV_MODE: false,
configCache: {}, configCache: {},
broadcastCache: {}, broadcastCache: {},
officeHeadersCache: undefined,
standardHeadersCache: undefined,
apiHeadersCache: undefined,
flushCache: function () { flushCache: function () {
Env.configCache = {}; Env.configCache = {};
Env.broadcastCache = {}; Env.broadcastCache = {};
Env.officeHeadersCache = undefined;
Env.standardHeadersCache = undefined;
Env.apiHeadersCache = undefined;
Env.FRESH_KEY = +new Date(); Env.FRESH_KEY = +new Date();
if (!(Env.DEV_MODE || Env.FRESH_MODE)) { Env.FRESH_MODE = true; } if (!(Env.DEV_MODE || Env.FRESH_MODE)) { Env.FRESH_MODE = true; }
if (!Env.Log) { return; } if (!Env.Log) { return; }

@ -48,9 +48,17 @@ var applyHeaderMap = function (res, map) {
for (let header in map) { res.setHeader(header, map[header]); } for (let header in map) { res.setHeader(header, map[header]); }
}; };
var setHeaders = (function () { var EXEMPT = [
// load the default http headers unless the admin has provided their own via the config file /^\/common\/onlyoffice\/.*\.html.*/,
var headers; /^\/(sheet|presentation|doc)\/inner\.html.*/,
/^\/unsafeiframe\/inner\.html.*$/,
];
var getHeaders = function (Env, type) {
var key = type + 'HeadersCache';
if (Env[key]) { return Env[key]; }
var headers = {};
var custom = config.httpHeaders; var custom = config.httpHeaders;
// if the admin provided valid http headers then use them // if the admin provided valid http headers then use them
@ -58,11 +66,11 @@ var setHeaders = (function () {
headers = Util.clone(custom); headers = Util.clone(custom);
} else { } else {
// otherwise use the default // otherwise use the default
headers = Default.httpHeaders(); headers = Default.httpHeaders(Env);
} }
// next define the base Content Security Policy (CSP) headers // next define the base Content Security Policy (CSP) headers
if (typeof(config.contentSecurity) === 'string') { if (typeof(config.contentSecurity) === 'string') { // XXX deprecate this???
headers['Content-Security-Policy'] = config.contentSecurity; headers['Content-Security-Policy'] = config.contentSecurity;
if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' } if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' }
if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) { if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) {
@ -73,47 +81,54 @@ var setHeaders = (function () {
} }
} else { } else {
// use the default CSP headers constructed with your domain // use the default CSP headers constructed with your domain
headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin, Env.httpSafeOrigin); headers['Content-Security-Policy'] = Default.contentSecurity(Env);
} }
const padHeaders = Util.clone(headers); //const padHeaders = Util.clone(headers);
if (typeof(config.padContentSecurity) === 'string') { if (type === 'office') {
padHeaders['Content-Security-Policy'] = config.padContentSecurity; if (typeof(config.padContentSecurity) === 'string') {
} else { headers['Content-Security-Policy'] = config.padContentSecurity; // XXX drop support for this
padHeaders['Content-Security-Policy'] = Default.padContentSecurity(Env.httpUnsafeOrigin, Env.httpSafeOrigin); } else {
headers['Content-Security-Policy'] = Default.padContentSecurity(Env);
}
}
/*
headers['Content-Security-Policy'] = type === 'office'?
Default.padContentSecurity(Env):
Default.contentSecurity(Env);*/
if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
headers["Cross-Origin-Resource-Policy"] = 'cross-origin';
headers["Cross-Origin-Embedder-Policy"] = 'require-corp';
} }
if (Object.keys(headers).length) {
return function (req, res) {
if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
}
// Don't set CSP headers on /api/ endpoints // Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems // because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments // when duplicated by NGINX in production environments
if (/^\/api\/(broadcast|config)/.test(req.url)) { return; } if (type === 'api') {
Env[key] = headers;
applyHeaderMap(res, { return headers;
"Cross-Origin-Resource-Policy": 'cross-origin',
});
// targeted CSP, generic policies, maybe custom headers
const h = [
/^\/common\/onlyoffice\/.*\.html.*/,
/^\/(sheet|presentation|doc)\/inner\.html.*/,
/^\/unsafeiframe\/inner\.html.*$/,
].some((regex) => {
return regex.test(req.url);
}) ? padHeaders : headers;
applyHeaderMap(res, h);
};
} }
return function () {};
}()); headers["Cross-Origin-Resource-Policy"] = 'cross-origin';
Env[key] = headers;
return headers;
};
var setHeaders = function (req, res) {
var type;
if (EXEMPT.some(regex => regex.test(req.url))) {
type = 'office';
} else if (/^\/api\/(broadcast|config)/.test(req.url)) {
type = 'api';
} else {
type = 'standard';
}
var h = getHeaders(Env, type);
applyHeaderMap(res, h);
};
(function () { (function () {
if (!config.logFeedback) { return; } if (!config.logFeedback) { return; }

@ -978,8 +978,8 @@ define([
'img-src': ["'self'", 'data:', 'blob:', $outer], 'img-src': ["'self'", 'data:', 'blob:', $outer],
'media-src': ['blob:'], 'media-src': ['blob:'],
//'frame-ancestors': ['*'], // XXX IFF you want to support remote embedding 'frame-ancestors': ApiConfig.disableEmbedding? [$outer, $sandbox]: ['*'],
'worker-src': ["'self'"], // , $outer, $sandbox], 'worker-src': ["'self'"],
}); });
cb(result); cb(result);
}); });
@ -1016,7 +1016,7 @@ define([
], ],
'img-src': ["'self'", 'data:', 'blob:', $outer], 'img-src': ["'self'", 'data:', 'blob:', $outer],
'media-src': ['blob:'], 'media-src': ['blob:'],
//'frame-ancestors': ['*'], // XXX IFF you want to support remote embedding 'frame-ancestors': ApiConfig.disableEmbedding? [$outer, $sandbox]: ['*'],
'worker-src': ["'self'"],//, $outer, $sandbox], 'worker-src': ["'self'"],//, $outer, $sandbox],
}); });
@ -1026,7 +1026,7 @@ define([
assert(function (cb, msg) { assert(function (cb, msg) {
var header = 'Access-Control-Allow-Origin'; var header = 'Access-Control-Allow-Origin';
msg.appendChild(h('span', [ msg.appendChild(h('span', [ // XXX update text to indicate that the value doesn't match their preference
'Assets must be served with an ', 'Assets must be served with an ',
code(header), code(header),
' header with a value of ', ' header with a value of ',
@ -1035,6 +1035,14 @@ define([
])); ]));
Tools.common_xhr('/', function (xhr) { Tools.common_xhr('/', function (xhr) {
var raw = xhr.getResponseHeader(header); var raw = xhr.getResponseHeader(header);
if (ApiConfig.disableEmbedding) {
if ([null, ''].includes(raw)) { return void cb(true); }
else {
return void cb(raw === '*' || raw);
}
}
cb(raw === "*" || raw); cb(raw === "*" || raw);
}); });
}); });

Loading…
Cancel
Save