Node.js - правильная политика безопасности содержимого для socket.io(веб-сокеты) с использованием шлема

Я пытаюсь внедрить политики безопасности контента (CSP) на сервере node, и у меня возникают проблемы с настройкой socket.io. Похоже, я неправильно настроил connectSrc в приведенном ниже коде. Может ли кто-нибудь предложить правильный способ создания шлема, чтобы браузерные сокеты разрешались браузером? Спасибо заранее!

Я использую модуль шлема для генерации CSP; ниже приведен код, который устанавливает CSP:

securitySetup = function(app) {
  var connectSources, helmet, scriptSources, styleSources;
  helmet = require("helmet");
  app.use(helmet());
  app.use(helmet.hidePoweredBy());
  app.use(helmet.noSniff());
  app.use(helmet.crossdomain());
  scriptSources = ["'self'", "'unsafe-inline'", "'unsafe-eval'", "ajax.googleapis.com"];
  styleSources = ["'self'", "'unsafe-inline'", "ajax.googleapis.com"];
  connectSources = ["'self'"];
  return app.use(helmet.contentSecurityPolicy({
    defaultSrc: ["'self'"],
    scriptSrc: scriptSources,
    styleSrc: styleSources,
    connectSrc: connectSources,
    reportUri: '/report-violation',
    reportOnly: false,
    setAllHeaders: false,
    safari5: false
  }));
};

Это отлично работает для всего трафика HTTP/AJAX, но не работает для протокола ws://. Я получаю эту ошибку в хром-отладчике при подключении socket.io:

Refused to connect to 'ws://localhost:3000/socket.io/1/websocket/ubexeZHZiAHwAV53WQ7u' because it violates the following Content Security Policy directive: "connect-src 'self'".

Chrome Console Error

Ответ 1

Добавление адреса с указанным протоколом решило проблему для меня.

connectSources = ["'self'", "ws://localhost:3000"]

Ответ 2

Тесное чтение Спецификации политики безопасности содержимого объясняет, почему 'self' не работает:

'self' соответствует запросам с тем же хостом, портом и схемой. Поскольку ваша исходная страница, загруженная схемой http:// или https://, 'self' не будет соответствовать соединениям с помощью ws://.

Это делает 'self' скорее бесполезным для соединений в сети. Если я что-то не хватает, я думаю, что это ошибка в спецификации.

Ответ 3

вы можете использовать ws: или wss: как ключевое слово для websocket, здесь используется мой код

app.use(helmet.csp({
  'default-src': ["'self'"],
  'connect-src': [
    "'self'" , "blob:",
    'wss:',
    'websocket.domain',
  ],
  'font-src': ["'self'",'s3.amazonaws.com',"maxcdn.bootstrapcdn.com"],
  'img-src': ["'self'", 'data:'],
  'style-src': ["'self'","maxcdn.bootstrapcdn.com",'s3.amazonaws.com',"'unsafe-inline'"],
  'script-src': ["'self'","'unsafe-inline'","'unsafe-eval'",'blob:'],
  reportOnly: false,
  setAllHeaders: false,
  safari5: false
}))

с wss://websocket.domain является доменом для websocket, поэтому, если вы можете использовать его для localhost

Ответ 4

Немного опоздал на вечеринку, но подумал, что я брошу что-то в микс по этой теме. @Ed4 совершенно прав, 'self' действительно будет соответствовать только одному хосту, порту и схеме. Путь к нему состоит в том, чтобы включить его в одну или несколько форм:

connect-src: wss: - разрешить соединение со всей схемой wss - в основном любой веб-сокет (возможно, не идеальный)

connect-src: wss://yoursite.domain.com - ограничить его конкретной конечной точкой. Это наиболее оптимально, но может быть ограничительным, если ваш поддомен изменяется между развертываниями (как это делает наш)

connect-src: wss://*.domain.com - можно использовать подстановочные знаки, чтобы немного закрепить защиту. Это то, что мы делаем

TL; DR - используйте подстановочные знаки, чтобы сделать вещи более конкретными, не открывая себя ни на какие веб-сокеты там /

Обратитесь к этому отрывку от разработчиков Google:

Список источников в каждой директиве является гибким. Вы можете указать источники по схеме (data:, https:) или варьироваться в зависимости от имени узла (example.com, которое соответствует любому происхождению на этом хосте: любая схема, любой порт) до полного URI (https://example.com:443, который соответствует только HTTPS, only example.com и только порт 443). Подстановочные знаки принимаются, но только в виде схемы, порта или в самой левой позиции имени хоста:://.example.com: * будут соответствовать всем поддоменам example.com(но не самому example.com), используя любые схемы на любом порту.

https://developers.google.com/web/fundamentals/security/csp/

Ответ 5

Здесь можно автоматически добавить текущий хост и порт в выражении (синтаксис es6):

import csp from 'helmet-csp';

app.use((req, res, next) => {
  let wsSrc = (req.protocol === 'http' ? 'ws://' : 'wss://') + req.get('host');

  csp({
    connectSrc: ['\'self\'', wsSrc],
  })(req, res, next);
});