Skip to content
Snippets Groups Projects
Commit 636fa3b2 authored by Michael Kozono's avatar Michael Kozono :two:
Browse files

Merge branch 'mk/silent-mode-block-outbound-gitlab-http' into 'master'

Silent Mode: Block many outbound HTTP requests

See merge request !120392



Merged-by: Michael Kozono's avatarMichael Kozono <mkozono@gitlab.com>
Approved-by: Matthias Käppler's avatarMatthias Käppler <mkaeppler@gitlab.com>
Approved-by: default avatarPedro Pombeiro <noreply@pedro.pombei.ro>
Approved-by: default avatarAmeya Darshan <adarshan@gitlab.com>
Reviewed-by: Michael Kozono's avatarMichael Kozono <mkozono@gitlab.com>
Reviewed-by: default avatarPedro Pombeiro <noreply@pedro.pombei.ro>
Reviewed-by: Matthias Käppler's avatarMatthias Käppler <mkaeppler@gitlab.com>
Co-authored-by: default avatarPedro Pombeiro <noreply@pedro.pombei.ro>
parents 62248b83 9d9cda91
No related branches found
No related tags found
No related merge requests found
Pipeline #872279198 passed
Pipeline: GitLab

#872291886

    ......@@ -33,6 +33,8 @@ There are two ways to enable Silent Mode:
    ::Gitlab::CurrentSettings.update!(silent_mode_enabled: true)
    ```
    It may take up to a minute to take effect. [Issue 405433](https://gitlab.com/gitlab-org/gitlab/-/issues/405433) proposes removing this delay.
    ## Disable Silent Mode
    Prerequisites:
    ......@@ -53,12 +55,26 @@ There are two ways to disable Silent Mode:
    ::Gitlab::CurrentSettings.update!(silent_mode_enabled: false)
    ```
    It may take up to a minute to take effect. [Issue 405433](https://gitlab.com/gitlab-org/gitlab/-/issues/405433) proposes removing this delay.
    ## Behavior of GitLab features in Silent Mode
    This section documents the current behavior of GitLab when Silent Mode is enabled. While Silent Mode is an Experiment, the behavior may change without notice. The work for the first iteration of Silent Mode is tracked by [Epic 9826](https://gitlab.com/groups/gitlab-org/-/epics/9826).
    ### Service Desk
    Incoming emails still raise issues, but the users who sent the emails to [Service Desk](../../user/project/service_desk.md) are not notified of issue creation or comments on their issues.
    ### Project and group webhooks
    Project and group webhooks are suppressed. The relevant Sidekiq jobs fail 4 times and then disappear, while Silent Mode is enabled. [Issue 393639](https://gitlab.com/gitlab-org/gitlab/-/issues/393639) discusses preventing the Sidekiq jobs from running in the first place.
    Triggering webhook tests via the UI results in HTTP status 500 responses.
    ### Outbound emails
    Outbound emails are suppressed. It may take up to a minute to take effect after enabling Silent Mode. [Issue 405433](https://gitlab.com/gitlab-org/gitlab/-/issues/405433) proposes removing this delay.
    Outbound emails are suppressed.
    ### Outbound HTTP requests
    Many outbound HTTP requests are suppressed. A list of unsuppressed requests does not exist at this time, since more suppression is planned.
    ......@@ -5,19 +5,17 @@ module Email
    module Hook
    class SilentModeInterceptor
    def self.delivering_email(message)
    if Gitlab::CurrentSettings.silent_mode_enabled?
    if ::Gitlab::SilentMode.enabled?
    message.perform_deliveries = false
    Gitlab::AppJsonLogger.info(
    ::Gitlab::SilentMode.log_info(
    message: "SilentModeInterceptor prevented sending mail",
    mail_subject: message.subject,
    silent_mode_enabled: true
    mail_subject: message.subject
    )
    else
    Gitlab::AppJsonLogger.debug(
    ::Gitlab::SilentMode.log_debug(
    message: "SilentModeInterceptor did nothing",
    mail_subject: message.subject,
    silent_mode_enabled: false
    mail_subject: message.subject
    )
    end
    end
    ......
    ......@@ -10,6 +10,7 @@ class HTTP
    RedirectionTooDeep = Class.new(StandardError)
    ReadTotalTimeout = Class.new(Net::ReadTimeout)
    HeaderReadTimeout = Class.new(Net::ReadTimeout)
    SilentModeBlockedError = Class.new(StandardError)
    HTTP_TIMEOUT_ERRORS = [
    Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Gitlab::HTTP::ReadTotalTimeout
    ......@@ -28,6 +29,13 @@ class HTTP
    }.freeze
    DEFAULT_READ_TOTAL_TIMEOUT = 30.seconds
    SILENT_MODE_ALLOWED_METHODS = [
    Net::HTTP::Get,
    Net::HTTP::Head,
    Net::HTTP::Options,
    Net::HTTP::Trace
    ].freeze
    include HTTParty # rubocop:disable Gitlab/HTTParty
    class << self
    ......@@ -37,6 +45,8 @@ class << self
    connection_adapter HTTPConnectionAdapter
    def self.perform_request(http_method, path, options, &block)
    raise_if_blocked_by_silent_mode(http_method)
    log_info = options.delete(:extra_log_info)
    options_with_timeouts =
    if !options.has_key?(:timeout)
    ......@@ -76,5 +86,20 @@ def self.try_get(path, options = {}, &block)
    rescue *HTTP_ERRORS
    nil
    end
    def self.raise_if_blocked_by_silent_mode(http_method)
    return unless blocked_by_silent_mode?(http_method)
    ::Gitlab::SilentMode.log_info(
    message: 'Outbound HTTP request blocked',
    outbound_http_request_method: http_method.to_s
    )
    raise SilentModeBlockedError, 'only get, head, options, and trace methods are allowed in silent mode'
    end
    def self.blocked_by_silent_mode?(http_method)
    ::Gitlab::SilentMode.enabled? && SILENT_MODE_ALLOWED_METHODS.exclude?(http_method)
    end
    end
    end
    # frozen_string_literal: true
    module Gitlab
    module SilentMode
    def self.enabled?
    Gitlab::CurrentSettings.silent_mode_enabled?
    end
    def self.log_info(data)
    Gitlab::AppJsonLogger.info(**add_silent_mode_log_data(data))
    end
    def self.log_debug(data)
    Gitlab::AppJsonLogger.debug(**add_silent_mode_log_data(data))
    end
    def self.add_silent_mode_log_data(data)
    data.merge!({ silent_mode_enabled: enabled? })
    end
    end
    end
    ......@@ -364,4 +364,77 @@ def read_body(*)
    end
    end
    end
    describe 'silent mode', feature_category: :geo_replication do
    before do
    stub_full_request("http://example.org", method: :any)
    stub_application_setting(silent_mode_enabled: silent_mode)
    end
    context 'when silent mode is enabled' do
    let(:silent_mode) { true }
    it 'allows GET requests' do
    expect { described_class.get('http://example.org') }.not_to raise_error
    end
    it 'allows HEAD requests' do
    expect { described_class.head('http://example.org') }.not_to raise_error
    end
    it 'allows OPTIONS requests' do
    expect { described_class.options('http://example.org') }.not_to raise_error
    end
    it 'blocks POST requests' do
    expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
    end
    it 'blocks PUT requests' do
    expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
    end
    it 'blocks DELETE requests' do
    expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
    end
    it 'logs blocked requests' do
    expect(::Gitlab::AppJsonLogger).to receive(:info).with(
    message: "Outbound HTTP request blocked",
    outbound_http_request_method: 'Net::HTTP::Post',
    silent_mode_enabled: true
    )
    expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
    end
    end
    context 'when silent mode is disabled' do
    let(:silent_mode) { false }
    it 'allows GET requests' do
    expect { described_class.get('http://example.org') }.not_to raise_error
    end
    it 'allows HEAD requests' do
    expect { described_class.head('http://example.org') }.not_to raise_error
    end
    it 'allows OPTIONS requests' do
    expect { described_class.options('http://example.org') }.not_to raise_error
    end
    it 'blocks POST requests' do
    expect { described_class.post('http://example.org') }.not_to raise_error
    end
    it 'blocks PUT requests' do
    expect { described_class.put('http://example.org') }.not_to raise_error
    end
    it 'blocks DELETE requests' do
    expect { described_class.delete('http://example.org') }.not_to raise_error
    end
    end
    end
    end
    # frozen_string_literal: true
    require 'spec_helper'
    RSpec.describe Gitlab::SilentMode, feature_category: :geo_replication do
    before do
    stub_application_setting(silent_mode_enabled: silent_mode)
    end
    describe '.enabled?' do
    context 'when silent mode is enabled' do
    let(:silent_mode) { true }
    it { expect(described_class.enabled?).to be_truthy }
    end
    context 'when silent mode is disabled' do
    let(:silent_mode) { false }
    it { expect(described_class.enabled?).to be_falsey }
    end
    end
    describe '.log_info' do
    let(:log_args) do
    {
    message: 'foo',
    bar: 'baz'
    }
    end
    let(:expected_log_args) { log_args.merge(silent_mode_enabled: silent_mode) }
    context 'when silent mode is enabled' do
    let(:silent_mode) { true }
    it 'logs to AppJsonLogger and adds the current state of silent mode' do
    expect(Gitlab::AppJsonLogger).to receive(:info).with(expected_log_args)
    described_class.log_info(log_args)
    end
    end
    context 'when silent mode is disabled' do
    let(:silent_mode) { false }
    it 'logs to AppJsonLogger and adds the current state of silent mode' do
    expect(Gitlab::AppJsonLogger).to receive(:info).with(expected_log_args)
    described_class.log_info(log_args)
    end
    it 'overwrites silent_mode_enabled log key if call already contains it' do
    expect(Gitlab::AppJsonLogger).to receive(:info).with(expected_log_args)
    described_class.log_info(log_args.merge(silent_mode_enabled: 'foo'))
    end
    end
    end
    describe '.log_debug' do
    let(:log_args) do
    {
    message: 'foo',
    bar: 'baz'
    }
    end
    let(:expected_log_args) { log_args.merge(silent_mode_enabled: silent_mode) }
    context 'when silent mode is enabled' do
    let(:silent_mode) { true }
    it 'logs to AppJsonLogger and adds the current state of silent mode' do
    expect(Gitlab::AppJsonLogger).to receive(:debug).with(expected_log_args)
    described_class.log_debug(log_args)
    end
    end
    context 'when silent mode is disabled' do
    let(:silent_mode) { false }
    it 'logs to AppJsonLogger and adds the current state of silent mode' do
    expect(Gitlab::AppJsonLogger).to receive(:debug).with(expected_log_args)
    described_class.log_debug(log_args)
    end
    it 'overwrites silent_mode_enabled log key if call already contains it' do
    expect(Gitlab::AppJsonLogger).to receive(:debug).with(expected_log_args)
    described_class.log_debug(log_args.merge(silent_mode_enabled: 'foo'))
    end
    end
    end
    end
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment