diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 08:17:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 08:17:02 +0000 |
commit | b39512ed755239198a9c294b6a45e65c05900235 (patch) | |
tree | d234a3efade1de67c46b9e5a38ce813627726aa7 /spec/lib/gitlab/ci/parsers | |
parent | d31474cf3b17ece37939d20082b07f6657cc79a9 (diff) | |
download | gitlab-ce-b39512ed755239198a9c294b6a45e65c05900235.tar.gz |
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci/parsers')
5 files changed, 633 insertions, 361 deletions
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb new file mode 100644 index 00000000000..c99cfa94aa6 --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do + subject(:parse_source) { described_class.parse_source(properties) } + + context 'when properties are nil' do + let(:properties) { nil } + + it { is_expected.to be_nil } + end + + context 'when report does not have gitlab properties' do + let(:properties) { ['name' => 'foo', 'value' => 'bar'] } + + it { is_expected.to be_nil } + end + + context 'when schema_version is missing' do + let(:properties) do + [ + { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' } + ] + end + + it { is_expected.to be_nil } + end + + context 'when schema version is unsupported' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '2' }, + { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' } + ] + end + + it { is_expected.to be_nil } + end + + context 'when no dependency_scanning properties are present' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' } + ] + end + + it 'does not call dependency_scanning parser' do + expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).not_to receive(:parse_source) + + parse_source + end + end + + context 'when dependency_scanning properties are present' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }, + { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' }, + { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' }, + { 'name' => 'gitlab:dependency_scanning:unsupported_property', 'value' => 'Should be ignored' } + ] + end + + let(:expected_input) do + { + 'category' => 'development', + 'input_file' => { 'path' => 'package-lock.json' }, + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it 'passes only supported properties to the dependency scanning parser' do + expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).to receive(:source).with(expected_input) + + parse_source + end + end +end diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb new file mode 100644 index 00000000000..431fe9f3591 --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx do + let(:report) { instance_double('Gitlab::Ci::Reports::Sbom::Report') } + let(:report_data) { base_report_data } + let(:raw_report_data) { report_data.to_json } + let(:report_valid?) { true } + let(:validator_errors) { [] } + let(:properties_parser) { class_double('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties') } + + let(:base_report_data) do + { + 'bomFormat' => 'CycloneDX', + 'specVersion' => '1.4', + 'version' => 1 + } + end + + subject(:parse!) { described_class.new.parse!(raw_report_data, report) } + + before do + allow_next_instance_of(Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator) do |validator| + allow(validator).to receive(:valid?).and_return(report_valid?) + allow(validator).to receive(:errors).and_return(validator_errors) + end + + allow(properties_parser).to receive(:parse_source) + stub_const('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties', properties_parser) + end + + context 'when report JSON is invalid' do + let(:raw_report_data) { '{ ' } + + it 'handles errors and adds them to the report' do + expect(report).to receive(:add_error).with(a_string_including("Report JSON is invalid:")) + + expect { parse! }.not_to raise_error + end + end + + context 'when report uses an unsupported spec version' do + let(:report_data) { base_report_data.merge({ 'specVersion' => '1.3' }) } + + it 'reports unsupported version as an error' do + expect(report).to receive(:add_error).with("Unsupported CycloneDX spec version. Must be one of: 1.4") + + parse! + end + end + + context 'when report does not conform to the CycloneDX schema' do + let(:report_valid?) { false } + let(:validator_errors) { %w[error1 error2] } + + it 'reports all errors returned by the validator' do + expect(report).to receive(:add_error).with("error1") + expect(report).to receive(:add_error).with("error2") + + parse! + end + end + + context 'when cyclonedx report has no components' do + it 'skips component processing' do + expect(report).not_to receive(:add_component) + + parse! + end + end + + context 'when report has components' do + let(:report_data) { base_report_data.merge({ 'components' => components }) } + let(:components) do + [ + { + "name" => "activesupport", + "version" => "5.1.4", + "purl" => "pkg:gem/activesupport@5.1.4", + "type" => "library", + "bom-ref" => "pkg:gem/activesupport@5.1.4" + }, + { + "name" => "byebug", + "version" => "10.0.0", + "purl" => "pkg:gem/byebug@10.0.0", + "type" => "library", + "bom-ref" => "pkg:gem/byebug@10.0.0" + }, + { + "name" => "minimal-component", + "type" => "library" + }, + { + # Should be skipped + "name" => "unrecognized-type", + "type" => "unknown" + } + ] + end + + it 'adds each component, ignoring unused attributes' do + expect(report).to receive(:add_component) + .with({ "name" => "activesupport", "version" => "5.1.4", "type" => "library" }) + expect(report).to receive(:add_component) + .with({ "name" => "byebug", "version" => "10.0.0", "type" => "library" }) + expect(report).to receive(:add_component) + .with({ "name" => "minimal-component", "type" => "library" }) + + parse! + end + end + + context 'when report has metadata properties' do + let(:report_data) { base_report_data.merge({ 'metadata' => { 'properties' => properties } }) } + + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }, + { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' }, + { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' } + ] + end + + it 'passes them to the properties parser' do + expect(properties_parser).to receive(:parse_source).with(properties) + + parse! + end + end +end diff --git a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb new file mode 100644 index 00000000000..30114b17cac --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do + subject { described_class.source(property_data) } + + context 'when all property data is present' do + let(:property_data) do + { + 'category' => 'development', + 'input_file' => { 'path' => 'package-lock.json' }, + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it 'returns expected source data' do + is_expected.to eq({ + 'type' => :dependency_scanning, + 'data' => property_data, + 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' + }) + end + end + + context 'when required properties are missing' do + let(:property_data) do + { + 'category' => 'development', + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it { is_expected.to be_nil } + end +end diff --git a/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb new file mode 100644 index 00000000000..c54a3268bbe --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator do + # Reports should be valid or invalid according to the specification at + # https://cyclonedx.org/docs/1.4/json/ + + subject(:validator) { described_class.new(report_data) } + + let_it_be(:required_attributes) do + { + "bomFormat" => "CycloneDX", + "specVersion" => "1.4", + "version" => 1 + } + end + + context "with minimally valid report" do + let_it_be(:report_data) { required_attributes } + + it { is_expected.to be_valid } + end + + context "when report has components" do + let(:report_data) { required_attributes.merge({ "components" => components }) } + + context "with minimally valid components" do + let(:components) do + [ + { + "type" => "library", + "name" => "activesupport" + }, + { + "type" => "library", + "name" => "byebug" + } + ] + end + + it { is_expected.to be_valid } + end + + context "when components have versions" do + let(:components) do + [ + { + "type" => "library", + "name" => "activesupport", + "version" => "5.1.4" + }, + { + "type" => "library", + "name" => "byebug", + "version" => "10.0.0" + } + ] + end + + it { is_expected.to be_valid } + end + + context "when components are not valid" do + let(:components) do + [ + { "type" => "foo" }, + { "name" => "activesupport" } + ] + end + + it { is_expected.not_to be_valid } + + it "outputs errors for each validation failure" do + expect(validator.errors).to match_array([ + "property '/components/0' is missing required keys: name", + "property '/components/0/type' is not one of: [\"application\", \"framework\"," \ + " \"library\", \"container\", \"operating-system\", \"device\", \"firmware\", \"file\"]", + "property '/components/1' is missing required keys: type" + ]) + end + end + end + + context "when report has metadata" do + let(:metadata) do + { + "timestamp" => "2022-02-23T08:02:39Z", + "tools" => [{ "vendor" => "GitLab", "name" => "Gemnasium", "version" => "2.34.0" }], + "authors" => [{ "name" => "GitLab", "email" => "support@gitlab.com" }] + } + end + + let(:report_data) { required_attributes.merge({ "metadata" => metadata }) } + + it { is_expected.to be_valid } + + context "when metadata has properties" do + before do + metadata.merge!({ "properties" => properties }) + end + + context "when properties are valid" do + let(:properties) do + [ + { "name" => "gitlab:dependency_scanning:input_file", "value" => "Gemfile.lock" }, + { "name" => "gitlab:dependency_scanning:package_manager", "value" => "bundler" } + ] + end + + it { is_expected.to be_valid } + end + + context "when properties are invalid" do + let(:properties) do + [ + { "name" => ["gitlab:meta:schema_version"], "value" => 1 } + ] + end + + it { is_expected.not_to be_valid } + + it "outputs errors for each validation failure" do + expect(validator.errors).to match_array([ + "property '/metadata/properties/0/name' is not of type: string", + "property '/metadata/properties/0/value' is not of type: string" + ]) + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb index d06077d69b6..7828aa99f6a 100644 --- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb @@ -6,6 +6,10 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let_it_be(:project) { create(:project) } let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') } + let(:deprecated_schema_version_message) {} + let(:missing_schema_version_message) do + "Report version not provided, dast report type supports versions: #{supported_dast_versions}" + end let(:scanner) do { @@ -24,7 +28,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do expect(described_class::SUPPORTED_VERSIONS.keys).to eq(described_class::DEPRECATED_VERSIONS.keys) end - context 'when a schema JSON file exists for a particular report type version' do + context 'when all files under schema path are explicitly listed' do # We only care about the part that comes before report-format.json # https://rubular.com/r/N8Juz7r8hYDYgD filename_regex = /(?<report_type>[-\w]*)\-report-format.json/ @@ -38,7 +42,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do matches = filename_regex.match(file) report_type = matches[:report_type].tr("-", "_").to_sym - it "#{report_type} #{version} is in the constant" do + it "#{report_type} #{version}" do expect(described_class::SUPPORTED_VERSIONS[report_type]).to include(version) end end @@ -64,11 +68,54 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do describe '#valid?' do subject { validator.valid? } + context 'when given a supported MAJOR.MINOR schema version' do + let(:report_type) { :dast } + let(:report_version) do + latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".") + (latest_vendored_version[0...2] << "34").join(".") + end + + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } + end + + it { is_expected.to be_truthy } + end + + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } + end + + it { is_expected.to be_falsey } + + it 'logs related information' do + expect(Gitlab::AppLogger).to receive(:info).with( + message: "security report schema validation problem", + security_report_type: report_type, + security_report_version: report_version, + project_id: project.id, + security_report_failure: 'schema_validation_fails', + security_report_scanner_id: 'gemnasium', + security_report_scanner_version: '2.1.0' + ) + + subject + end + end + end + context 'when given a supported schema version' do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -79,7 +126,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_truthy } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version @@ -118,7 +165,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => '10.0.0', @@ -143,34 +190,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end end - context 'when the report does not pass schema validation' do - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_falsey } + context 'and the report does not pass schema validation' do + let(:report_data) do + { + 'version' => 'V2.7.0' + } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_truthy } - end + it { is_expected.to be_falsey } end end @@ -178,20 +205,40 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { "12.37.0" } - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end + it { is_expected.to be_falsey } + + it 'logs related information' do + expect(Gitlab::AppLogger).to receive(:info).with( + message: "security report schema validation problem", + security_report_type: report_type, + security_report_version: report_version, + project_id: project.id, + security_report_failure: 'using_unsupported_schema_version', + security_report_scanner_id: 'gemnasium', + security_report_scanner_version: '2.1.0' + ) - it { is_expected.to be_falsey } + subject + end + end + + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } + end + + context 'and scanner information is empty' do + let(:scanner) { {} } it 'logs related information' do expect(Gitlab::AppLogger).to receive(:info).with( @@ -199,79 +246,26 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do security_report_type: report_type, security_report_version: report_version, project_id: project.id, + security_report_failure: 'schema_validation_fails', + security_report_scanner_id: nil, + security_report_scanner_version: nil + ) + + expect(Gitlab::AppLogger).to receive(:info).with( + message: "security report schema validation problem", + security_report_type: report_type, + security_report_version: report_version, + project_id: project.id, security_report_failure: 'using_unsupported_schema_version', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' + security_report_scanner_id: nil, + security_report_scanner_version: nil ) subject end end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - context 'when scanner information is empty' do - let(:scanner) { {} } - - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'schema_validation_fails', - security_report_scanner_id: nil, - security_report_scanner_version: nil - ) - - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'using_unsupported_schema_version', - security_report_scanner_id: nil, - security_report_scanner_version: nil - ) - - subject - end - end - - it { is_expected.to be_falsey } - end - end - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_truthy } - end - - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_truthy } - end + it { is_expected.to be_falsey } end end @@ -284,19 +278,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - before do - stub_feature_flags(enforce_security_report_validation: true) - end - it { is_expected.to be_falsey } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_truthy } - end end end @@ -307,7 +289,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -318,34 +300,20 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: project) - end - - let(:expected_errors) do - [ - 'root is missing required keys: vulnerabilities' - ] - end - - it { is_expected.to match_array(expected_errors) } + let(:expected_errors) do + [ + 'root is missing required keys: vulnerabilities' + ] end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_empty } - end + it { is_expected.to match_array(expected_errors) } end end @@ -363,7 +331,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => '10.0.0', @@ -374,119 +342,77 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report does not pass schema validation' do - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - let(:expected_errors) do - [ - "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_errors) } + context 'and the report does not pass schema validation' do + let(:report_data) do + { + 'version' => 'V2.7.0' + } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_empty } + let(:expected_errors) do + [ + "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", + "root is missing required keys: vulnerabilities" + ] end + + it { is_expected.to match_array(expected_errors) } end end context 'when given an unsupported schema version' do let(:report_type) { :dast } let(:report_version) { "12.37.0" } + let(:expected_unsupported_message) do + "Version #{report_version} for report type #{report_type} is unsupported, supported versions for this report type are: "\ + "#{supported_dast_versions}. GitLab will attempt to validate this report against the earliest supported "\ + "versions of this report type, to show all the errors but will not ingest the report" + end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - let(:expected_errors) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_errors) } + let(:expected_errors) do + [ + expected_unsupported_message + ] end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - let(:expected_errors) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_errors) } - end + it { is_expected.to match_array(expected_errors) } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } + let(:expected_errors) do + [ + expected_unsupported_message, + "root is missing required keys: vulnerabilities" + ] end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_empty } - end + it { is_expected.to match_array(expected_errors) } end end context 'when not given a schema version' do let(:report_type) { :dast } let(:report_version) { nil } + let(:expected_missing_version_message) do + "Report version not provided, #{report_type} report type supports versions: #{supported_dast_versions}. GitLab "\ + "will attempt to validate this report against the earliest supported versions of this report type, to show all "\ + "the errors but will not ingest the report" + end + let(:report_data) do { 'vulnerabilities' => [] @@ -496,19 +422,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:expected_errors) do [ "root is missing required keys: version", - "Report version not provided, dast report type supports versions: #{supported_dast_versions}" + expected_missing_version_message ] end it { is_expected.to match_array(expected_errors) } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_empty } - end end end @@ -519,7 +437,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -530,7 +448,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version @@ -550,9 +468,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end let(:report_version) { described_class::DEPRECATED_VERSIONS[report_type].last } + let(:expected_deprecation_message) do + "Version #{report_version} for report type #{report_type} has been deprecated, supported versions for this "\ + "report type are: #{supported_dast_versions}. GitLab will attempt to parse and ingest this report if valid." + end + let(:expected_deprecation_warnings) do [ - "Version V2.7.0 for report type dast has been deprecated, supported versions for this report type are: #{supported_dast_versions}" + expected_deprecation_message ] end @@ -560,7 +483,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => report_version, @@ -571,7 +494,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to match_array(expected_deprecation_warnings) } end - context 'when the report does not pass schema validation' do + context 'and the report does not pass schema validation' do let(:report_data) do { 'version' => 'V2.7.0' @@ -600,11 +523,27 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do describe '#warnings' do subject { validator.warnings } - context 'when given a supported schema version' do + context 'when given a supported MAJOR.MINOR schema version' do let(:report_type) { :dast } - let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } + let(:report_version) do + latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".") + (latest_vendored_version[0...2] << "34").join(".") + end + + let(:latest_patch_version) do + ::Security::ReportSchemaVersionMatcher.new( + report_declared_version: report_version, + supported_versions: described_class::SUPPORTED_VERSIONS[report_type] + ).call + end + + let(:message) do + "This report uses a supported MAJOR.MINOR schema version but the PATCH version doesn't match"\ + " any vendored schema version. Validation will be attempted against version"\ + " #{latest_patch_version}" + end - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -612,37 +551,57 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - it { is_expected.to be_empty } + it { is_expected.to match_array([message]) } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: project) - end + it { is_expected.to match_array([message]) } + + it 'logs related information' do + expect(Gitlab::AppLogger).to receive(:info).with( + message: "security report schema validation problem", + security_report_type: report_type, + security_report_version: report_version, + project_id: project.id, + security_report_failure: 'schema_validation_fails', + security_report_scanner_id: 'gemnasium', + security_report_scanner_version: '2.1.0' + ) - it { is_expected.to be_empty } + subject end + end + end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end + context 'when given a supported schema version' do + let(:report_type) { :dast } + let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - let(:expected_warnings) do - [ - 'root is missing required keys: vulnerabilities' - ] - end + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } + end + + it { is_expected.to be_empty } + end - it { is_expected.to match_array(expected_warnings) } + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end + + it { is_expected.to be_empty } end end @@ -660,7 +619,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'vulnerabilities' => [] @@ -670,35 +629,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report does not pass schema validation' do + context 'and the report does not pass schema validation' do let(:report_data) do { 'version' => 'V2.7.0' } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - it { is_expected.to be_empty } - end - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:expected_warnings) do - [ - "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_warnings) } - end + it { is_expected.to be_empty } end end @@ -706,71 +644,25 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { "12.37.0" } - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_empty } - end + it { is_expected.to be_empty } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - let(:expected_warnings) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_warnings) } + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - let(:expected_warnings) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_warnings) } - end + it { is_expected.to be_empty } end end @@ -784,21 +676,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end it { is_expected.to be_empty } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:expected_warnings) do - [ - "root is missing required keys: version", - "Report version not provided, dast report type supports versions: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_warnings) } - end end end end |