Skip to content
Snippets Groups Projects
Commit 9d438d37 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot
Browse files

Merge branch 'security-lm-mask-variables-in-error-14-7' into '14-7-stable-ee'

Masks variables in error messages

See merge request gitlab-org/security/gitlab!2292
parents 31295303 1706c5cf
No related branches found
No related tags found
No related merge requests found
Showing
with 112 additions and 43 deletions
Loading
Loading
@@ -70,6 +70,16 @@ module Gitlab
}
end
 
def mask_variables_from(location)
variables.reduce(location.dup) do |loc, variable|
if variable[:masked]
Gitlab::Ci::MaskSecret.mask!(loc, variable[:value])
else
loc
end
end
end
protected
 
attr_writer :expandset, :execution_deadline, :logger
Loading
Loading
Loading
Loading
@@ -37,7 +37,7 @@ module Gitlab
def validate_content!
return unless ensure_preconditions_satisfied!
 
errors.push("File `#{location}` is empty!") unless content.present?
errors.push("File `#{masked_location}` is empty!") unless content.present?
end
 
def ensure_preconditions_satisfied!
Loading
Loading
Loading
Loading
@@ -79,21 +79,21 @@ module Gitlab
 
def validate_location!
if invalid_location_type?
errors.push("Included file `#{location}` needs to be a string")
errors.push("Included file `#{masked_location}` needs to be a string")
elsif invalid_extension?
errors.push("Included file `#{location}` does not have YAML extension!")
errors.push("Included file `#{masked_location}` does not have YAML extension!")
end
end
 
def validate_content!
if content.blank?
errors.push("Included file `#{location}` is empty or does not exist!")
errors.push("Included file `#{masked_location}` is empty or does not exist!")
end
end
 
def validate_hash!
if to_hash.blank?
errors.push("Included file `#{location}` does not have valid YAML syntax!")
errors.push("Included file `#{masked_location}` does not have valid YAML syntax!")
end
end
 
Loading
Loading
@@ -104,6 +104,12 @@ module Gitlab
def expand_context_attrs
{}
end
def masked_location
strong_memoize(:masked_location) do
context.mask_variables_from(location)
end
end
end
end
end
Loading
Loading
Loading
Loading
@@ -23,11 +23,11 @@ module Gitlab
 
def validate_content!
if context.project&.repository.nil?
errors.push("Local file `#{location}` does not have project!")
errors.push("Local file `#{masked_location}` does not have project!")
elsif content.nil?
errors.push("Local file `#{location}` does not exist!")
errors.push("Local file `#{masked_location}` does not exist!")
elsif content.blank?
errors.push("Local file `#{location}` is empty!")
errors.push("Local file `#{masked_location}` is empty!")
end
end
 
Loading
Loading
Loading
Loading
@@ -35,9 +35,9 @@ module Gitlab
elsif sha.nil?
errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
elsif content.nil?
errors.push("Project `#{project_name}` file `#{location}` does not exist!")
errors.push("Project `#{project_name}` file `#{masked_location}` does not exist!")
elsif content.blank?
errors.push("Project `#{project_name}` file `#{location}` is empty!")
errors.push("Project `#{project_name}` file `#{masked_location}` is empty!")
end
end
 
Loading
Loading
Loading
Loading
@@ -24,7 +24,7 @@ module Gitlab
super
 
unless ::Gitlab::UrlSanitizer.valid?(location)
errors.push("Remote file `#{location}` does not have a valid address!")
errors.push("Remote file `#{masked_location}` does not have a valid address!")
end
end
 
Loading
Loading
@@ -32,17 +32,17 @@ module Gitlab
begin
response = Gitlab::HTTP.get(location)
rescue SocketError
errors.push("Remote file `#{location}` could not be fetched because of a socket error!")
errors.push("Remote file `#{masked_location}` could not be fetched because of a socket error!")
rescue Timeout::Error
errors.push("Remote file `#{location}` could not be fetched because of a timeout error!")
errors.push("Remote file `#{masked_location}` could not be fetched because of a timeout error!")
rescue Gitlab::HTTP::Error
errors.push("Remote file `#{location}` could not be fetched because of HTTP error!")
errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP error!")
rescue Gitlab::HTTP::BlockedUrlError => e
errors.push("Remote file could not be fetched because #{e}!")
end
 
if response&.code.to_i >= 400
errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!")
errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP code `#{response.code}` error!")
end
 
response.body if errors.none?
Loading
Loading
Loading
Loading
@@ -26,7 +26,7 @@ module Gitlab
super
 
unless template_name_valid?
errors.push("Template file `#{location}` is not a valid location!")
errors.push("Template file `#{masked_location}` is not a valid location!")
end
end
 
Loading
Loading
Loading
Loading
@@ -146,7 +146,7 @@ module Gitlab
file_class.new(location, context)
end.select(&:matching?)
 
raise AmbigiousSpecificationError, "Include `#{location.to_json}` needs to match exactly one accessor!" unless matching.one?
raise AmbigiousSpecificationError, "Include `#{masked_location(location.to_json)}` needs to match exactly one accessor!" unless matching.one?
 
matching.first
end
Loading
Loading
@@ -181,6 +181,10 @@ module Gitlab
def expand(data)
ExpandVariables.expand(data, -> { context.variables_hash })
end
def masked_location(location)
context.mask_variables_from(location)
end
end
end
end
Loading
Loading
Loading
Loading
@@ -127,6 +127,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
 
context 'when file is empty' do
let(:params) { { artifact: 'secret_stuff/generated.yml', job: 'generator' } }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_stuff', 'masked' => true }]) }
let(:context) do
Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline, variables: variables)
end
before do
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
allow(reader).to receive(:read).and_return('')
Loading
Loading
@@ -134,7 +140,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
end
 
let(:expected_error) do
'File `generated.yml` is empty!'
'File `xxxxxxxxxxxx/generated.yml` is empty!'
end
 
it_behaves_like 'is invalid'
Loading
Loading
Loading
Loading
@@ -3,7 +3,8 @@
require 'spec_helper'
 
RSpec.describe Gitlab::Ci::Config::External::File::Base do
let(:context_params) { { sha: 'HEAD' } }
let(:variables) { }
let(:context_params) { { sha: 'HEAD', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
 
let(:test_class) do
Loading
Loading
@@ -76,7 +77,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
end
 
context 'when there are YAML syntax errors' do
let(:location) { 'some/file/config.yml' }
let(:location) { 'some/file/secret_file_name.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file_name', 'masked' => true }]) }
 
before do
allow_any_instance_of(test_class)
Loading
Loading
@@ -85,7 +87,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
 
it 'is not a valid file' do
expect(subject).not_to be_valid
expect(subject.error_message).to match /does not have valid YAML syntax/
expect(subject.error_message).to eq('Included file `some/file/xxxxxxxxxxxxxxxx.yml` does not have valid YAML syntax!')
end
end
end
Loading
Loading
Loading
Loading
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
let_it_be(:user) { create(:user) }
 
let(:sha) { '12345' }
let(:variables) { project.predefined_variables.to_runner_variables }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
Loading
Loading
@@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
sha: sha,
user: user,
parent_pipeline: parent_pipeline,
variables: project.predefined_variables.to_runner_variables
variables: variables
}
end
 
Loading
Loading
@@ -66,7 +67,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end
end
 
context 'when is not a valid local path' do
context 'when it is not a valid local path' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
 
it 'returns false' do
Loading
Loading
@@ -74,13 +75,23 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end
end
 
context 'when is not a yaml file' do
context 'when it is not a yaml file' do
let(:location) { '/config/application.rb' }
 
it 'returns false' do
expect(local_file.valid?).to be_falsy
end
end
context 'when it is an empty file' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret', 'masked' => true }]) }
let(:location) { '/lib/gitlab/ci/templates/secret/existent-file.yml' }
it 'returns false and adds an error message about an empty file' do
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("")
expect(local_file.errors).to include("Local file `/lib/gitlab/ci/templates/xxxxxx/existent-file.yml` is empty!")
end
end
end
 
describe '#content' do
Loading
Loading
@@ -116,10 +127,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end
 
describe '#error_message' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:location) { '/lib/gitlab/ci/templates/secret_file.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
 
it 'returns an error message' do
expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
expect(local_file.error_message).to eq("Local file `/lib/gitlab/ci/templates/xxxxxxxxxxx.yml` does not exist!")
end
end
 
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
let(:parent_pipeline) { double(:parent_pipeline) }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) }
let(:variables) { project.predefined_variables.to_runner_variables }
 
let(:context_params) do
{
Loading
Loading
@@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
sha: '12345',
user: context_user,
parent_pipeline: parent_pipeline,
variables: project.predefined_variables.to_runner_variables
variables: variables
}
end
 
Loading
Loading
@@ -108,18 +109,19 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
 
context 'when an empty file is used' do
let(:params) do
{ project: project.full_path, file: '/file.yml' }
{ project: project.full_path, file: '/secret_file.yml' }
end
 
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
let(:root_ref_sha) { project.repository.root_ref_sha }
 
before do
stub_project_blob(root_ref_sha, '/file.yml') { '' }
stub_project_blob(root_ref_sha, '/secret_file.yml') { '' }
end
 
it 'returns false' do
expect(project_file).not_to be_valid
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!")
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxx.yml` is empty!")
end
end
 
Loading
Loading
@@ -135,13 +137,15 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
end
 
context 'when non-existing file is requested' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-invalid-file', 'masked' => true }]) }
let(:params) do
{ project: project.full_path, file: '/invalid-file.yml' }
{ project: project.full_path, file: '/secret-invalid-file.yml' }
end
 
it 'returns false' do
expect(project_file).not_to be_valid
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!")
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxxxxxxxxxx.yml` does not exist!")
end
end
 
Loading
Loading
Loading
Loading
@@ -5,11 +5,12 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
 
let(:context_params) { { sha: '12345' } }
let(:variables) {Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
let(:context_params) { { sha: '12345', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.secret_file.yml' }
let(:remote_file_content) do
<<~HEREDOC
before_script:
Loading
Loading
@@ -144,10 +145,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
subject { remote_file.error_message }
 
context 'when remote file location is not valid' do
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?secret_file.yml' }
 
it 'returns an error message describing invalid address' do
expect(subject).to match /does not have a valid address!/
expect(subject).to eq('Remote file `not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?xxxxxxxxxxx.yml` does not have a valid address!')
end
end
 
Loading
Loading
@@ -157,7 +158,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end
 
it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of a timeout error!/
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of a timeout error!')
end
end
 
Loading
Loading
@@ -167,7 +168,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end
 
it 'returns error message about a HTTP error' do
expect(subject).to match /could not be fetched because of HTTP error!/
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP error!')
end
end
 
Loading
Loading
@@ -177,7 +178,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end
 
it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of HTTP code `404` error!/
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP code `404` error!')
end
end
 
Loading
Loading
Loading
Loading
@@ -54,11 +54,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template do
end
 
context 'with invalid template name' do
let(:template) { 'Template.yml' }
let(:template) { 'SecretTemplate.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'SecretTemplate', 'masked' => true }]) }
let(:context_params) { { project: project, sha: '12345', user: user, variables: variables } }
 
it 'returns false' do
expect(template_file).not_to be_valid
expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!')
expect(template_file.error_message).to include('`xxxxxxxxxxxxxx.yml` is not a valid location!')
end
end
 
Loading
Loading
Loading
Loading
@@ -11,7 +11,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables } }
let(:variables) { project.predefined_variables }
let(:context_params) { { project: project, sha: '123456', user: user, variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
 
let(:file_content) do
Loading
Loading
@@ -92,13 +93,16 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
 
context 'when the key is a hash of file and remote' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-file', 'masked' => true }]) }
let(:local_file) { 'secret-file.yml' }
let(:remote_url) { 'https://gitlab.com/secret-file.yml' }
let(:values) do
{ include: { 'local' => local_file, 'remote' => remote_url },
image: 'ruby:2.7' }
end
 
it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Include `{"local":"xxxxxxxxxxx.yml","remote":"https://gitlab.com/xxxxxxxxxxx.yml"}` needs to match exactly one accessor!')
end
end
 
Loading
Loading
Loading
Loading
@@ -53,6 +53,24 @@ RSpec.describe Ci::CreatePipelineService do
end
 
context 'when failed to create the pipeline' do
context 'when errors are raised and masked variables are involved' do
let_it_be(:variable) { create(:ci_variable, project: project, key: 'GL_TOKEN', value: 'test_value', masked: true) }
let(:config) do
<<~YAML
include:
- local: $GL_TOKEN/gitlab-ci.txt
YAML
end
it 'contains errors and masks variables' do
error_message = "Included file `xxxxxxxxxx/gitlab-ci.txt` does not have YAML extension!"
expect(pipeline.yaml_errors).to eq(error_message)
expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
expect(pipeline.errors.full_messages).to contain_exactly(error_message)
end
end
context 'when warnings are raised' do
let(:config) do
<<~YAML
Loading
Loading
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