Commit c644d94f authored by Adam Hegyi's avatar Adam Hegyi Committed by GitLab Release Tools Bot

Security fix sentry issue leaks and access level check

Merge branch 'security-fix-sentry-issue-leaks-and-access-level-check-14-10' into '14-10-stable-ee'

See merge request gitlab-org/security/gitlab!2501

Changelog: security
parent 43645436
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug']; export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug'];
export const transformFrontendSettings = ({ export const transformFrontendSettings = ({
apiHost, apiHost,
...@@ -9,6 +9,7 @@ export const transformFrontendSettings = ({ ...@@ -9,6 +9,7 @@ export const transformFrontendSettings = ({
}) => { }) => {
const project = selectedProject const project = selectedProject
? { ? {
sentry_project_id: selectedProject.id,
slug: selectedProject.slug, slug: selectedProject.slug,
name: selectedProject.name, name: selectedProject.name,
organization_name: selectedProject.organizationName, organization_name: selectedProject.organizationName,
......
...@@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle ...@@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
respond_to :json respond_to :json
before_action :authorize_read_sentry_issue! before_action :authorize_read_sentry_issue!
before_action :authorize_update_sentry_issue!, only: %i[update]
before_action :set_issue_id, only: :details before_action :set_issue_id, only: :details
before_action only: [:index] do before_action only: [:index] do
......
...@@ -143,7 +143,7 @@ module Projects ...@@ -143,7 +143,7 @@ module Projects
:integrated, :integrated,
:api_host, :api_host,
:token, :token,
project: [:slug, :name, :organization_slug, :organization_name] project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id]
], ],
grafana_integration_attributes: [:token, :grafana_url, :enabled], grafana_integration_attributes: [:token, :grafana_url, :enabled],
......
...@@ -298,6 +298,7 @@ module ProjectsHelper ...@@ -298,6 +298,7 @@ module ProjectsHelper
setting.organization_slug.blank? setting.organization_slug.blank?
{ {
sentry_project_id: setting.sentry_project_id,
name: setting.project_name, name: setting.project_name,
organization_name: setting.organization_name, organization_name: setting.organization_name,
organization_slug: setting.organization_slug, organization_slug: setting.organization_slug,
......
...@@ -125,17 +125,22 @@ module ErrorTracking ...@@ -125,17 +125,22 @@ module ErrorTracking
def issue_details(opts = {}) def issue_details(opts = {})
with_reactive_cache('issue_details', opts.stringify_keys) do |result| with_reactive_cache('issue_details', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:issue].project_id)
result result
end end
end end
def issue_latest_event(opts = {}) def issue_latest_event(opts = {})
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result| with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:latest_event].project_id)
result result
end end
end end
def update_issue(opts = {}) def update_issue(opts = {})
issue_to_be_updated = sentry_client.issue_details(issue_id: opts[:issue_id])
ensure_issue_belongs_to_project!(issue_to_be_updated.project_id)
handle_exceptions do handle_exceptions do
{ updated: sentry_client.update_issue(opts) } { updated: sentry_client.update_issue(opts) }
end end
...@@ -177,6 +182,25 @@ module ErrorTracking ...@@ -177,6 +182,25 @@ module ErrorTracking
private private
def ensure_issue_belongs_to_project!(project_id_from_api)
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
end
def ensure_sentry_project_id!
return sentry_project_id if sentry_project_id.present?
raise("Couldn't find project: #{organization_name} / #{project_name} on Sentry") if sentry_project.nil?
update!(sentry_project_id: sentry_project.id)
sentry_project_id
end
def sentry_project
strong_memoize(:sentry_project) do
sentry_client.projects.find { |project| project.name == project_name && project.organization_name == organization_name }
end
end
def add_gitlab_issue_details(issue) def add_gitlab_issue_details(issue)
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version) issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit
......
...@@ -297,7 +297,6 @@ class ProjectPolicy < BasePolicy ...@@ -297,7 +297,6 @@ class ProjectPolicy < BasePolicy
enable :read_deployment enable :read_deployment
enable :read_merge_request enable :read_merge_request
enable :read_sentry_issue enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus enable :read_prometheus
enable :read_metrics_dashboard_annotation enable :read_metrics_dashboard_annotation
enable :metrics_dashboard enable :metrics_dashboard
...@@ -413,6 +412,7 @@ class ProjectPolicy < BasePolicy ...@@ -413,6 +412,7 @@ class ProjectPolicy < BasePolicy
enable :admin_feature_flags_user_lists enable :admin_feature_flags_user_lists
enable :update_escalation_status enable :update_escalation_status
enable :read_secure_files enable :read_secure_files
enable :update_sentry_issue
end end
rule { can?(:developer_access) & user_confirmed? }.policy do rule { can?(:developer_access) & user_confirmed? }.policy do
......
...@@ -90,7 +90,8 @@ module Projects ...@@ -90,7 +90,8 @@ module Projects
api_url: api_url, api_url: api_url,
enabled: settings[:enabled], enabled: settings[:enabled],
project_name: settings.dig(:project, :name), project_name: settings.dig(:project, :name),
organization_name: settings.dig(:project, :organization_name) organization_name: settings.dig(:project, :organization_name),
sentry_project_id: settings.dig(:project, :sentry_project_id)
} }
} }
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value
......
# frozen_string_literal: true
class AddSentryProjectIdToProjectErrorTrackingSettings < Gitlab::Database::Migration[2.0]
def change
add_column :project_error_tracking_settings, :sentry_project_id, :bigint
end
end
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5
\ No newline at end of file
...@@ -19156,7 +19156,8 @@ CREATE TABLE project_error_tracking_settings ( ...@@ -19156,7 +19156,8 @@ CREATE TABLE project_error_tracking_settings (
encrypted_token_iv character varying, encrypted_token_iv character varying,
project_name character varying, project_name character varying,
organization_name character varying, organization_name character varying,
integrated boolean DEFAULT true NOT NULL integrated boolean DEFAULT true NOT NULL,
sentry_project_id bigint
); );
CREATE TABLE project_export_jobs ( CREATE TABLE project_export_jobs (
...@@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section. ...@@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section.
## Taking Action on errors ## Taking Action on errors
You can take action on Sentry Errors from within the GitLab UI. You can take action on Sentry Errors from within the GitLab UI. Marking errors ignored or resolved require at least Developer role.
### Ignoring errors ### Ignoring errors
......
...@@ -15,6 +15,7 @@ module ErrorTracking ...@@ -15,6 +15,7 @@ module ErrorTracking
stack_trace = parse_stack_trace(event) stack_trace = parse_stack_trace(event)
Gitlab::ErrorTracking::ErrorEvent.new( Gitlab::ErrorTracking::ErrorEvent.new(
project_id: event['projectID'],
issue_id: event['groupID'], issue_id: event['groupID'],
date_received: event['dateReceived'], date_received: event['dateReceived'],
stack_trace_entries: stack_trace stack_trace_entries: stack_trace
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
class ErrorEvent class ErrorEvent
include ActiveModel::Model include ActiveModel::Model
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project, :project_id
def self.declarative_policy_class def self.declarative_policy_class
'ErrorTracking::BasePolicy' 'ErrorTracking::BasePolicy'
......
...@@ -297,40 +297,54 @@ RSpec.describe Projects::ErrorTrackingController do ...@@ -297,40 +297,54 @@ RSpec.describe Projects::ErrorTrackingController do
put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json) put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json)
end end
before do
expect(ErrorTracking::IssueUpdateService)
.to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
end
describe 'format json' do describe 'format json' do
context 'update result is successful' do context 'when user is a reporter' do
before do before do
expect(issue_update_service).to receive(:execute) project.add_reporter(user)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid) end
it 'returns 404 error' do
update_issue update_issue
end
it 'returns a success' do expect(response).to have_gitlab_http_status(:not_found)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/update_issue')
end end
end end
context 'update result is erroneous' do context 'when user has access to update' do
let(:error_message) { 'error message' }
before do before do
expect(issue_update_service).to receive(:execute) expect(ErrorTracking::IssueUpdateService)
.and_return(status: :error, message: error_message) .to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
end
update_issue context 'when update result is successful' do
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid)
update_issue
end
it 'returns a success' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/update_issue')
end
end end
it 'returns 400 with message' do context 'update result is erroneous' do
expect(response).to have_gitlab_http_status(:bad_request) let(:error_message) { 'error message' }
expect(json_response['message']).to eq(error_message)
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :error, message: error_message)
update_issue
end
it 'returns 400 with message' do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(error_message)
end
end end
end end
end end
......
...@@ -72,6 +72,7 @@ RSpec.describe 'Database schema' do ...@@ -72,6 +72,7 @@ RSpec.describe 'Database schema' do
oauth_applications: %w[owner_id], oauth_applications: %w[owner_id],
product_analytics_events_experimental: %w[event_id txn_id user_id], product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id], project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id], project_group_links: %w[group_id],
project_statistics: %w[namespace_id], project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id], projects: %w[creator_id ci_id mirror_user_id],
......
...@@ -13,7 +13,7 @@ FactoryBot.define do ...@@ -13,7 +13,7 @@ FactoryBot.define do
message { 'message' } message { 'message' }
culprit { 'culprit' } culprit { 'culprit' }
external_url { 'http://example.com/id' } external_url { 'http://example.com/id' }
project_id { 'project1' } project_id { '111111' }
project_name { 'project name' } project_name { 'project name' }
project_slug { 'project_name' } project_slug { 'project_name' }
short_id { 'ID' } short_id { 'ID' }
......
...@@ -9,6 +9,7 @@ FactoryBot.define do ...@@ -9,6 +9,7 @@ FactoryBot.define do
project_name { 'Sentry Project' } project_name { 'Sentry Project' }
organization_name { 'Sentry Org' } organization_name { 'Sentry Org' }
integrated { false } integrated { false }
sentry_project_id { 10 }
trait :disabled do trait :disabled do
enabled { false } enabled { false }
......
...@@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do ...@@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
context 'api_url present' do context 'api_url present' do
let(:json) do let(:json) do
{ {
sentry_project_id: error_tracking_setting.sentry_project_id,
name: error_tracking_setting.project_name, name: error_tracking_setting.project_name,
organization_name: error_tracking_setting.organization_name, organization_name: error_tracking_setting.organization_name,
organization_slug: error_tracking_setting.organization_slug, organization_slug: error_tracking_setting.organization_slug,
......
...@@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
let(:sentry_client) { instance_double(ErrorTracking::SentryClient) } let(:sentry_client) { instance_double(ErrorTracking::SentryClient) }
subject(:setting) { build(:project_error_tracking_setting, project: project) } let(:sentry_project_id) { 10 }
subject(:setting) { build(:project_error_tracking_setting, project: project, sentry_project_id: sentry_project_id) }
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
...@@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
describe '#issue_details' do describe '#issue_details' do
let(:issue) { build(:error_tracking_sentry_detailed_error) } let(:issue) { build(:error_tracking_sentry_detailed_error, project_id: sentry_project_id) }
let(:commit_id) { issue.first_release_version } let(:commit_id) { issue.first_release_version }
let(:result) { subject.issue_details(opts) } let(:result) { subject.issue_details(opts) }
let(:opts) { { issue_id: 1 } } let(:opts) { { issue_id: 1 } }
...@@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
end end
describe '#issue_latest_event' do
let(:error_event) { build(:error_tracking_sentry_error_event, project_id: sentry_project_id) }
let(:result) { subject.issue_latest_event(opts) }
let(:opts) { { issue_id: 1 } }
before do
stub_reactive_cache(subject, error_event, {})
synchronous_reactive_cache(subject)
allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_latest_event).with(opts).and_return(error_event)
end
it 'returns the error event' do
expect(result[:latest_event].project_id).to eq(sentry_project_id)
end
end
describe '#update_issue' do describe '#update_issue' do
let(:result) { subject.update_issue(**opts) } let(:result) { subject.update_issue(**opts) }
let(:opts) { { issue_id: 1, params: {} } } let(:opts) { { issue_id: 1, params: {} } }
before do before do
allow(subject).to receive(:sentry_client).and_return(sentry_client) allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_details)
.with({ issue_id: 1 })
.and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id))
end end
context 'when sentry response is successful' do context 'when sentry response is successful' do
...@@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
expect(result).to eq(error: 'Unexpected Error') expect(result).to eq(error: 'Unexpected Error')
end end
end end
context 'when sentry_project_id is not set' do
let(:sentry_projects) do
[
Gitlab::ErrorTracking::Project.new(
id: 1111,
name: 'Some Project',
organization_name: 'Org'
),
Gitlab::ErrorTracking::Project.new(
id: sentry_project_id,
name: setting.project_name,
organization_name: setting.organization_name
)
]
end
context 'when sentry_project_id is not set' do
before do
setting.update!(sentry_project_id: nil)
allow(sentry_client).to receive(:projects).and_return(sentry_projects)
allow(sentry_client).to receive(:update_issue).with(opts).and_return(true)
end
it 'tries to backfill it from sentry API' do
expect(result).to eq(updated: true)
expect(setting.reload.sentry_project_id).to eq(sentry_project_id)
end
context 'when the project cannot be found on sentry' do
before do
sentry_projects.pop
end
it 'raises error' do
expect { result }.to raise_error(/Couldn't find project/)
end
end
end
context 'when mismatching sentry_project_id is detected' do
it 'raises error' do
setting.update!(sentry_project_id: sentry_project_id + 1)
expect { result }.to raise_error(/The Sentry issue appers to be outside/)
end
end
end
end end
describe 'slugs' do describe 'slugs' do
......
...@@ -2102,4 +2102,25 @@ RSpec.describe ProjectPolicy do ...@@ -2102,4 +2102,25 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:register_project_runners) } it { is_expected.to be_disallowed(:register_project_runners) }
end end
end end
describe 'update_sentry_issue' do
using RSpec::Parameterized::TableSyntax
where(:role, :allowed) do
:owner | true
:maintainer | true
:developer | true
:reporter | false
:guest | false
end
let(:project) { public_project }
let(:current_user) { public_send(role) }
with_them do
it do
expect(subject.can?(:update_sentry_issue)).to be(allowed)
end
end
end
end end
...@@ -294,7 +294,7 @@ RSpec.describe Issues::CreateService do ...@@ -294,7 +294,7 @@ RSpec.describe Issues::CreateService do
context 'user is reporter or above' do context 'user is reporter or above' do
before do before do
project.add_reporter(user) project.add_developer(user)
end end
it 'assigns the sentry error' do it 'assigns the sentry error' do
......
...@@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do ...@@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do
before do before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting) expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user) project.add_developer(user)
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment