diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index bdcedd1896d7ccd9792783fd523357b4da8531ec..0e3fa8b8d8700e9bb4c116cfe08c0d572cbfdddc 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -35,6 +35,10 @@ def self.default_directives # However Safari seems to read child-src first so we'll just keep both equal directives['child_src'] = directives['frame_src'] + # connect_src with 'self' includes https/wss variations of the origin, + # however, safari hasn't covered this yet and we need to explicitly add + # support for websocket origins until Safari catches up with the specs + allow_websocket_connections(directives) allow_webpack_dev_server(directives) if Rails.env.development? allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present? allow_customersdot(directives) if Rails.env.development? && ENV['CUSTOMER_PORTAL_URL'].present? @@ -67,6 +71,22 @@ def arguments_for(directive) arguments.strip.split(' ').map(&:strip) end + def self.allow_websocket_connections(directives) + http_ports = [80, 443] + host = Gitlab.config.gitlab.host + port = Gitlab.config.gitlab.port + secure = Gitlab.config.gitlab.https + protocol = secure ? 'wss' : 'ws' + + ws_url = "#{protocol}://#{host}" + + unless http_ports.include?(port) + ws_url = "#{ws_url}:#{port}" + end + + append_to_directive(directives, 'connect_src', ws_url) + end + def self.allow_webpack_dev_server(directives) secure = Settings.webpack.dev_server['https'] host_and_port = "#{Settings.webpack.dev_server['host']}:#{Settings.webpack.dev_server['port']}" diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 239eff11bf3c2373a9bc2927fcdcd19cd28c4c77..3ec332dace594b2f9a1d35ad9aa0551c4b5f7ae0 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -11,6 +11,7 @@ directives: { base_uri: 'http://example.com', child_src: "'self' https://child.example.com", + connect_src: "'self' ws://example.com", default_src: "'self' https://other.example.com", script_src: "'self' https://script.exammple.com ", worker_src: "data: https://worker.example.com", @@ -52,6 +53,28 @@ expect(directives['child_src']).to eq(directives['frame_src']) end + context 'adds all websocket origins to support Safari' do + it 'with insecure domain' do + stub_config_setting(host: 'example.com', https: false) + expect(directives['connect_src']).to eq("'self' ws://example.com") + end + + it 'with secure domain' do + stub_config_setting(host: 'example.com', https: true) + expect(directives['connect_src']).to eq("'self' wss://example.com") + end + + it 'with custom port' do + stub_config_setting(host: 'example.com', port: '1234') + expect(directives['connect_src']).to eq("'self' ws://example.com:1234") + end + + it 'with custom port and secure domain' do + stub_config_setting(host: 'example.com', https: true, port: '1234') + expect(directives['connect_src']).to eq("'self' wss://example.com:1234") + end + end + context 'when CDN host is defined' do before do stub_config_setting(cdn_host: 'https://example.com') @@ -67,10 +90,11 @@ context 'when sentry is configured' do before do stub_sentry_settings + stub_config_setting(host: 'example.com') end it 'adds sentry path to CSP without user' do - expect(directives['connect_src']).to eq("'self' dummy://example.com/43") + expect(directives['connect_src']).to eq("'self' ws://example.com dummy://example.com/43") end end @@ -113,6 +137,7 @@ def expected_config(directive) expect(policy.directives['base-uri']).to eq([csp_config[:directives][:base_uri]]) expect(policy.directives['default-src']).to eq(expected_config(:default_src)) + expect(policy.directives['connect-src']).to eq(expected_config(:connect_src)) expect(policy.directives['child-src']).to eq(expected_config(:child_src)) expect(policy.directives['worker-src']).to eq(expected_config(:worker_src)) expect(policy.directives['report-uri']).to eq(expected_config(:report_uri))