Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-19 21:10:45 +00:00
parent 83e4339a32
commit f265a70317
66 changed files with 1833 additions and 154 deletions

View File

@ -1,7 +1,7 @@
<!--
# Read me first!
Create this issue under https://gitlab.com/gitlab-org/security
Create this issue under https://gitlab.com/gitlab-org/security/gitlab
Set the title to: `Description of the original issue`
-->

View File

@ -1 +1 @@
6a06feda7fd01961bb332afce4d7f7b4ce4a5aad
99f78e4d93d8c9ec23ef710ffde0fb4b75d786bb

View File

@ -1,5 +1,5 @@
<script>
import { GlLoadingIcon, GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import { GlLoadingIcon, GlIcon, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import { n__, __ } from '~/locale';
import { DESIGN_ROUTE_NAME } from '../../router/constants';
@ -11,6 +11,9 @@ export default {
GlIcon,
Timeago,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
id: {
type: [Number, String],
@ -130,7 +133,7 @@ export default {
<div
class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative"
>
<div v-if="icon.name" data-testid="designEvent" class="design-event gl-absolute">
<div v-if="icon.name" data-testid="design-event" class="design-event gl-absolute">
<span :title="icon.tooltip" :aria-label="icon.tooltip">
<gl-icon
:name="icon.name"
@ -153,9 +156,10 @@ export default {
v-show="showImage"
:src="imageLink"
:alt="filename"
class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img"
class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
data-qa-selector="design_image"
:data-qa-filename="filename"
:data-testid="`design-img-${id}`"
@load="onImageLoad"
@error="onImageError"
/>
@ -163,9 +167,14 @@ export default {
</div>
<div class="card-footer gl-display-flex gl-w-full">
<div class="gl-display-flex gl-flex-direction-column str-truncated-100">
<span class="gl-font-weight-bold str-truncated-100" data-qa-selector="design_file_name">{{
filename
}}</span>
<span
v-gl-tooltip
class="gl-font-weight-bold str-truncated-100"
data-qa-selector="design_file_name"
:data-testid="`design-img-filename-${id}`"
:title="filename"
>{{ filename }}</span
>
<span v-if="updatedAt" class="str-truncated-100">
{{ __('Updated') }} <timeago :time="updatedAt" tooltip-placement="bottom" />
</span>

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class AnalyzersEntityInputType < BaseInputObject
graphql_name 'SastCiConfigurationAnalyzersEntityInput'
description 'Represents the analyzers entity in SAST CI configuration'
argument :name, GraphQL::STRING_TYPE, required: true,
description: 'Name of analyzer.'
argument :enabled, GraphQL::BOOLEAN_TYPE, required: true,
description: 'State of the analyzer.'
argument :variables, [::Types::CiConfiguration::Sast::EntityInputType],
description: 'List of variables for the analyzer.',
required: false
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class AnalyzersEntityType < BaseObject
graphql_name 'SastCiConfigurationAnalyzersEntity'
description 'Represents an analyzer entity in SAST CI configuration'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the analyzer.'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Analyzer label used in the config UI.'
field :enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates whether an analyzer is enabled.'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Analyzer description that is displayed on the form.'
field :variables, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
description: 'List of supported variables.'
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class EntityInputType < BaseInputObject
graphql_name 'SastCiConfigurationEntityInput'
description 'Represents an entity in SAST CI configuration'
argument :field, GraphQL::STRING_TYPE, required: true,
description: 'CI keyword of entity.'
argument :default_value, GraphQL::STRING_TYPE, required: true,
description: 'Default value that is used if value is empty.'
argument :value, GraphQL::STRING_TYPE, required: true,
description: 'Current value of the entity.'
end
end
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class EntityType < BaseObject
graphql_name 'SastCiConfigurationEntity'
description 'Represents an entity in SAST CI configuration'
field :field, GraphQL::STRING_TYPE, null: true,
description: 'CI keyword of entity.'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Label for entity used in the form.'
field :type, GraphQL::STRING_TYPE, null: true,
description: 'Type of the field value.'
field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true,
description: 'Different possible values of the field.'
field :default_value, GraphQL::STRING_TYPE, null: true,
description: 'Default value that is used if value is empty.'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Entity description that is displayed on the form.'
field :value, GraphQL::STRING_TYPE, null: true,
description: 'Current value of the entity.'
field :size, ::Types::CiConfiguration::Sast::UiComponentSizeEnum, null: true,
description: 'Size of the UI component.'
end
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
class InputType < BaseInputObject # rubocop:disable Graphql/AuthorizeTypes
graphql_name 'SastCiConfigurationInput'
description 'Represents a CI configuration of SAST'
argument :global, [::Types::CiConfiguration::Sast::EntityInputType],
description: 'List of global entities related to SAST configuration.',
required: false
argument :pipeline, [::Types::CiConfiguration::Sast::EntityInputType],
description: 'List of pipeline entities related to SAST configuration.',
required: false
argument :analyzers, [::Types::CiConfiguration::Sast::AnalyzersEntityInputType],
description: 'List of analyzers and related variables for the SAST configuration.',
required: false
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class OptionsEntityType < BaseObject
graphql_name 'SastCiConfigurationOptionsEntity'
description 'Represents an entity for options in SAST CI configuration'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Label of option entity.'
field :value, GraphQL::STRING_TYPE, null: true,
description: 'Value of option entity.'
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class Type < BaseObject
graphql_name 'SastCiConfiguration'
description 'Represents a CI configuration of SAST'
field :global, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
description: 'List of global entities related to SAST configuration.'
field :pipeline, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
description: 'List of pipeline entities related to SAST configuration.'
field :analyzers, ::Types::CiConfiguration::Sast::AnalyzersEntityType.connection_type, null: true,
description: 'List of analyzers entities attached to SAST configuration.'
end
end
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
class UiComponentSizeEnum < BaseEnum
graphql_name 'SastUiComponentSize'
description 'Size of UI component in SAST configuration page'
value 'SMALL'
value 'MEDIUM'
value 'LARGE'
end
end
end
end

View File

@ -16,6 +16,10 @@ module Types
field :path, GraphQL::STRING_TYPE, null: false,
description: 'Path of the project'
field :sast_ci_configuration, Types::CiConfiguration::Sast::Type, null: true,
calls_gitaly: true,
description: 'SAST CI configuration for the project'
field :name_with_namespace, GraphQL::STRING_TYPE, null: false,
description: 'Full name of the project with its namespace'
field :name, GraphQL::STRING_TYPE, null: false,
@ -359,6 +363,12 @@ module Types
project.container_repositories.size
end
def sast_ci_configuration
return unless Ability.allowed?(current_user, :download_code, object)
::Security::CiConfiguration::SastParserService.new(object).configuration
end
private
def project

View File

@ -995,13 +995,21 @@ module Ci
end
def has_coverage_reports?
pipeline_artifacts&.has_code_coverage?
pipeline_artifacts&.has_report?(:code_coverage)
end
def can_generate_coverage_reports?
has_reports?(Ci::JobArtifact.coverage_reports)
end
def has_codequality_reports?
pipeline_artifacts&.has_report?(:code_quality)
end
def can_generate_codequality_reports?
has_reports?(Ci::JobArtifact.codequality_reports)
end
def test_report_summary
strong_memoize(:test_report_summary) do
Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)

View File

@ -30,15 +30,18 @@ module Ci
update_project_statistics project_statistics_name: :pipeline_artifacts_size
enum file_type: {
code_coverage: 1
code_coverage: 1,
code_quality: 2
}
def self.has_code_coverage?
where(file_type: :code_coverage).exists?
end
class << self
def has_report?(file_type)
where(file_type: file_type).exists?
end
def self.find_with_code_coverage
find_by(file_type: :code_coverage)
def find_by_file_type(file_type)
find_by(file_type: file_type)
end
end
def present

View File

@ -12,7 +12,7 @@ module Ci
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: head_pipeline.pipeline_artifacts.find_with_code_coverage.present.for_files(merge_request.new_paths)
data: head_pipeline.pipeline_artifacts.find_by_file_type(:code_coverage).present.for_files(merge_request.new_paths)
}
rescue => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
module Security
module CiConfiguration
# This class parses SAST template file and .gitlab-ci.yml to populate default and current values into the JSON
# read from app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
class SastParserService < ::BaseService
include Gitlab::Utils::StrongMemoize
SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json'
def initialize(project)
@project = project
end
def configuration
result = Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH))).with_indifferent_access
populate_default_value_for(result, :global)
populate_default_value_for(result, :pipeline)
fill_current_value_with_default_for(result, :global)
fill_current_value_with_default_for(result, :pipeline)
populate_current_value_for(result, :global)
populate_current_value_for(result, :pipeline)
fill_current_value_with_default_for_analyzers(result)
populate_current_value_for_analyzers(result)
result
end
private
def sast_template_content
Gitlab::Template::GitlabCiYmlTemplate.find('SAST').content
end
def populate_default_value_for(config, level)
set_each(config[level], key: :default_value, with: sast_template_attributes)
end
def populate_current_value_for(config, level)
set_each(config[level], key: :value, with: gitlab_ci_yml_attributes)
end
def fill_current_value_with_default_for(config, level)
set_each(config[level], key: :value, with: sast_template_attributes)
end
def set_each(config_attributes, key:, with:)
config_attributes.each do |entity|
entity[key] = with[entity[:field]] if with[entity[:field]]
end
end
def fill_current_value_with_default_for_analyzers(result)
result[:analyzers].each do |analyzer|
analyzer[:variables].each do |entity|
entity[:value] = entity[:default_value] if entity[:default_value]
end
end
end
def populate_current_value_for_analyzers(result)
result[:analyzers].each do |analyzer|
analyzer[:enabled] = analyzer_enabled?(analyzer[:name])
populate_current_value_for(analyzer, :variables)
end
end
def analyzer_enabled?(analyzer_name)
# Unless explicitly listed in the excluded analyzers, consider it enabled
sast_excluded_analyzers.exclude?(analyzer_name)
end
def sast_excluded_analyzers
strong_memoize(:sast_excluded_analyzers) do
all_analyzers = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS.split(', ') rescue []
enabled_analyzers = sast_default_analyzers.split(',').map(&:strip) rescue []
excluded_analyzers = gitlab_ci_yml_attributes["SAST_EXCLUDED_ANALYZERS"] || sast_template_attributes["SAST_EXCLUDED_ANALYZERS"]
excluded_analyzers = excluded_analyzers.split(',').map(&:strip) rescue []
((all_analyzers - enabled_analyzers) + excluded_analyzers).uniq
end
end
def sast_default_analyzers
@sast_default_analyzers ||= gitlab_ci_yml_attributes["SAST_DEFAULT_ANALYZERS"] || sast_template_attributes["SAST_DEFAULT_ANALYZERS"]
end
def sast_template_attributes
@sast_template_attributes ||= build_sast_attributes(sast_template_content)
end
def gitlab_ci_yml_attributes
@gitlab_ci_yml_attributes ||= begin
config_content = @project.repository.blob_data_at(@project.repository.root_ref_sha, ci_config_file)
return {} unless config_content
build_sast_attributes(config_content)
end
end
def ci_config_file
'.gitlab-ci.yml'
end
def build_sast_attributes(content)
options = { project: @project, user: current_user, sha: @project.repository.commit.sha }
yaml_result = Gitlab::Ci::YamlProcessor.new(content, options).execute
return {} unless yaml_result.valid?
sast_attributes = yaml_result.build_attributes(:sast)
extract_required_attributes(sast_attributes)
end
def extract_required_attributes(attributes)
result = {}
attributes[:yaml_variables].each do |variable|
result[variable[:key]] = variable[:value]
end
result[:stage] = attributes[:stage]
result.with_indifferent_access
end
end
end
end

View File

@ -6,7 +6,7 @@
%button.btn.js-settings-toggle
= expanded ? _('Collapse') : _('Expand')
%p
= _('Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one.')
= _('Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one.')
.settings-content
= form_for @project, remote: true, html: { multipart: true, anchor: 'default-branch-settings' }, authenticity_token: true do |f|
@ -25,7 +25,7 @@
= f.label :autoclose_referenced_issues, class: 'form-check-label' do
%strong= _("Auto-close referenced issues on default branch")
.form-text.text-muted
= _("Issues referenced by merge requests and commits within the default branch will be closed automatically")
= _("When merge requests and commits in the default branch close, any issues they reference also close.")
= link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank'
= f.submit _('Save changes'), class: "gl-button btn btn-success"

View File

@ -9,6 +9,6 @@
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
= _("Finish editing this message first!")
= submit_tag _('Save comment'), class: 'btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' }
%button.btn.btn-cancel.note-edit-cancel{ type: 'button' }
= submit_tag _('Save comment'), class: 'gl-button btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' }
%button.btn.gl-button.btn-cancel.note-edit-cancel{ type: 'button' }
= _("Cancel")

View File

@ -38,5 +38,5 @@
.note-form-actions.clearfix
= render partial: 'shared/notes/comment_button'
%a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } }
%a.btn.gl-button.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } }
= _('Cancel')

View File

@ -0,0 +1,5 @@
---
title: Adds GitLab UI styles to button in _edit_form.html.haml
merge_request: 51156
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Adds GitLab UI styles to button in _form.html.haml
merge_request: 51160
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Display full design name in tooltip
merge_request: 51421
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Updated UI text to match style guidelines
merge_request: 51658
author:
type: other

View File

@ -1,5 +1,13 @@
# frozen_string_literal: true
if Gitlab::Runtime.puma? && ::Puma.cli_config.options[:workers].to_i == 0
return if allow_single_mode?
raise 'Puma is only supported in Cluster-mode: workers > 0'
end
def allow_single_mode?
return false if Gitlab.com?
Gitlab::Utils.to_boolean(ENV['PUMA_SKIP_CLUSTER_VALIDATION'])
end

View File

@ -157,3 +157,4 @@ exceptions:
- XML
- XSS
- YAML
- ZIP

View File

@ -4,22 +4,24 @@ group: Health
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# GitLab Configuration
# GitLab configuration
GitLab Performance Monitoring is disabled by default. To enable it and change any of its
settings:
1. Navigate to **Admin Area > Settings > Metrics and profiling**
(`/admin/application_settings/metrics_and_profiling`):
![GitLab Performance Monitoring Administration Settings](img/metrics_gitlab_configuration_settings.png)
1. You must restart all GitLab processes for the changes to take effect:
(`/admin/application_settings/metrics_and_profiling`).
1. Add the necessary configuration changes.
1. Restart all GitLab for the changes to take effect:
- For Omnibus GitLab installations: `sudo gitlab-ctl restart`
- For installations from source: `sudo service gitlab restart`
## Pending Migrations
NOTE:
Removed [in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30786). Use the
[Prometheus integration](../prometheus/index.md) instead.
## Pending migrations
When any migrations are pending, the metrics are disabled until the migrations
have been performed.

View File

@ -17,42 +17,38 @@ or Grafana supplies package repositories (Yum/Apt) for easy installation.
See [Grafana installation documentation](https://grafana.com/docs/grafana/latest/installation/)
for detailed steps.
Before starting Grafana for the first time, set the admin user
Before starting Grafana for the first time, set the administration user
and password in `/etc/grafana/grafana.ini`. If you don't, the default password
is `admin`.
## Configuration
1. Log in to Grafana as the admin user.
1. Expand the menu by clicking the Grafana logo in the top left corner.
1. Choose **Data Sources** from the menu.
1. Click **Add new** in the top bar:
![Grafana empty data source page](img/grafana_data_source_empty.png)
1. Edit the data source to fit your needs:
![Grafana data source configurations](img/grafana_data_source_configuration.png)
1. Click **Save**.
1. Log in to Grafana as the administration user.
1. Select **Data Sources** from the **Configuration** menu.
1. Select the **Add data source** button.
1. Select the required data source type. For example, [Prometheus](../prometheus/index.md#prometheus-as-a-grafana-data-source).
1. Complete the details for the data source and select the **Save & Test** button.
Grafana should indicate the data source is working.
## Import Dashboards
You can now import a set of default dashboards to start displaying useful information.
GitLab has published a set of default
[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started.
Clone the repository, or download a ZIP file or tarball, then follow these steps to import each
JSON file individually:
[Grafana dashboards](https://gitlab.com/gitlab-org/grafana-dashboards) to get you started. To use
them:
1. Log in to Grafana as the admin user.
1. Open the dashboard dropdown menu and click **Import**:
![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png)
1. Click **Choose file**, and browse to the location where you downloaded or
cloned the dashboard repository. Select a JSON file to import:
![Grafana dashboard import](img/grafana_dashboard_import.png)
1. After the dashboard is imported, click the **Save dashboard** icon in the top bar:
![Grafana save icon](img/grafana_save_icon.png)
1. Clone the repository, or download a ZIP file or tarball.
1. Follow these steps to import each JSON file individually:
If you don't save the dashboard after importing it, the dashboard is removed
when you navigate away from the page.
1. Log in to Grafana as the administration user.
1. Select **Manage** from the **Dashboards** menu.
1. Select the **Import** button, then the **Upload JSON file** button.
1. Locate the JSON file to import and select **Choose for Upload**. Select the **Import** button.
1. After the dashboard is imported, select the **Save dashboard** icon in the top bar.
Repeat this process for each dashboard you wish to import.
If you don't save the dashboard after importing it, the dashboard is removed
when you navigate away from the page. Repeat this process for each dashboard you wish to import.
Alternatively, you can import all the dashboards into your Grafana
instance. For more information about this process, see the
@ -103,7 +99,7 @@ sudo mv /var/opt/gitlab/grafana/data.bak.xxxx/ /var/opt/gitlab/grafana/data/
However, you should **not** reinstate your old data _except_ under one of the following conditions:
1. If you're certain that you changed your default admin password when you enabled Grafana.
1. If you're certain that you changed your default administration password when you enabled Grafana.
1. If you run GitLab in a private network, accessed only by trusted users, and your
Grafana login page has not been exposed to the internet.
@ -121,8 +117,6 @@ existing data and dashboards.
For more information and further mitigation details, please refer to our
[blog post on the security release](https://about.gitlab.com/releases/2019/08/12/critical-security-release-gitlab-12-dot-1-dot-6-released/).
---
Read more on:
- [Introduction to GitLab Performance Monitoring](index.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -6,6 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Monitoring GitLab with Prometheus
[Prometheus](https://prometheus.io) is a powerful time-series monitoring service, providing a flexible
platform for monitoring GitLab and other software products.
GitLab provides out-of-the-box monitoring with Prometheus, providing easy
access to high quality time-series monitoring of GitLab services.
> **Notes:**
>
> - Prometheus and the various exporters listed in this page are bundled in the
@ -16,11 +21,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - Prometheus and its exporters don't authenticate users, and are available
> to anyone who can access them.
[Prometheus](https://prometheus.io) is a powerful time-series monitoring service, providing a flexible
platform for monitoring GitLab and other software products.
GitLab provides out of the box monitoring with Prometheus, providing easy
access to high quality time-series monitoring of GitLab services.
## Overview
Prometheus works by periodically connecting to data sources and collecting their

View File

@ -222,7 +222,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `api_secret_key` | Full path to file with secret key used to authenticate with the GitLab API. Auto-generated when left unset.
| `artifacts_server` | Enable viewing [artifacts](../job_artifacts.md) in GitLab Pages.
| `artifacts_server_timeout` | Timeout (in seconds) for a proxied request to the artifacts server.
| `artifacts_server_url` | API URL to proxy artifact requests to. Defaults to GitLab `external URL` + `/api/v4`, for example `https://gitlab.com/api/v4`.
| `artifacts_server_url` | API URL to proxy artifact requests to. Defaults to GitLab `external URL` + `/api/v4`, for example `https://gitlab.com/api/v4`. When running a [separate Pages server](#running-gitlab-pages-on-a-separate-server), this URL must point to the main GitLab server's API.
| `auth_redirect_uri` | Callback URL for authenticating with GitLab. Defaults to project's subdomain of `pages_external_url` + `/auth`.
| `auth_secret` | Secret key for signing authentication requests. Leave blank to pull automatically from GitLab during OAuth registration.
| `dir` | Working directory for configuration and secrets files.
@ -940,6 +940,8 @@ Upgrading to an [officially supported operating system](https://about.gitlab.com
This problem comes from the permissions of the GitLab Pages OAuth application. To fix it, go to
**Admin > Applications > GitLab Pages** and edit the application. Under **Scopes**, ensure that the
`api` scope is selected and save your changes.
When running a [separate Pages server](#running-gitlab-pages-on-a-separate-server),
this setting needs to be configured on the main GitLab server.
### Workaround in case no wildcard DNS entry can be set

View File

@ -22102,27 +22102,27 @@ Represents an analyzer entity in SAST CI configuration
"""
type SastCiConfigurationAnalyzersEntity {
"""
Analyzer description that is displayed on the form
Analyzer description that is displayed on the form.
"""
description: String
"""
Indicates whether an analyzer is enabled
Indicates whether an analyzer is enabled.
"""
enabled: Boolean
"""
Analyzer label used in the config UI
Analyzer label used in the config UI.
"""
label: String
"""
Name of the analyzer
Name of the analyzer.
"""
name: String
"""
List of supported variables
List of supported variables.
"""
variables(
"""
@ -22187,17 +22187,17 @@ Represents the analyzers entity in SAST CI configuration
"""
input SastCiConfigurationAnalyzersEntityInput {
"""
State of the analyzer
State of the analyzer.
"""
enabled: Boolean!
"""
Name of analyzer
Name of analyzer.
"""
name: String!
"""
List of variables for the analyzer
List of variables for the analyzer.
"""
variables: [SastCiConfigurationEntityInput!]
}
@ -22307,17 +22307,17 @@ Represents an entity in SAST CI configuration
"""
input SastCiConfigurationEntityInput {
"""
Default value that is used if value is empty
Default value that is used if value is empty.
"""
defaultValue: String!
"""
CI keyword of entity
CI keyword of entity.
"""
field: String!
"""
Current value of the entity
Current value of the entity.
"""
value: String!
}
@ -22327,17 +22327,17 @@ Represents a CI configuration of SAST
"""
input SastCiConfigurationInput {
"""
List of analyzers and related variables for the SAST configuration
List of analyzers and related variables for the SAST configuration.
"""
analyzers: [SastCiConfigurationAnalyzersEntityInput!]
"""
List of global entities related to SAST configuration
List of global entities related to SAST configuration.
"""
global: [SastCiConfigurationEntityInput!]
"""
List of pipeline entities related to SAST configuration
List of pipeline entities related to SAST configuration.
"""
pipeline: [SastCiConfigurationEntityInput!]
}

View File

@ -63880,7 +63880,7 @@
"fields": [
{
"name": "description",
"description": "Analyzer description that is displayed on the form",
"description": "Analyzer description that is displayed on the form.",
"args": [
],
@ -63894,7 +63894,7 @@
},
{
"name": "enabled",
"description": "Indicates whether an analyzer is enabled",
"description": "Indicates whether an analyzer is enabled.",
"args": [
],
@ -63908,7 +63908,7 @@
},
{
"name": "label",
"description": "Analyzer label used in the config UI",
"description": "Analyzer label used in the config UI.",
"args": [
],
@ -63922,7 +63922,7 @@
},
{
"name": "name",
"description": "Name of the analyzer",
"description": "Name of the analyzer.",
"args": [
],
@ -63936,7 +63936,7 @@
},
{
"name": "variables",
"description": "List of supported variables",
"description": "List of supported variables.",
"args": [
{
"name": "after",
@ -64115,7 +64115,7 @@
"inputFields": [
{
"name": "name",
"description": "Name of analyzer",
"description": "Name of analyzer.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -64129,7 +64129,7 @@
},
{
"name": "enabled",
"description": "State of the analyzer",
"description": "State of the analyzer.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -64143,7 +64143,7 @@
},
{
"name": "variables",
"description": "List of variables for the analyzer",
"description": "List of variables for the analyzer.",
"type": {
"kind": "LIST",
"name": null,
@ -64448,7 +64448,7 @@
"inputFields": [
{
"name": "field",
"description": "CI keyword of entity",
"description": "CI keyword of entity.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -64462,7 +64462,7 @@
},
{
"name": "defaultValue",
"description": "Default value that is used if value is empty",
"description": "Default value that is used if value is empty.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -64476,7 +64476,7 @@
},
{
"name": "value",
"description": "Current value of the entity",
"description": "Current value of the entity.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -64501,7 +64501,7 @@
"inputFields": [
{
"name": "global",
"description": "List of global entities related to SAST configuration",
"description": "List of global entities related to SAST configuration.",
"type": {
"kind": "LIST",
"name": null,
@ -64519,7 +64519,7 @@
},
{
"name": "pipeline",
"description": "List of pipeline entities related to SAST configuration",
"description": "List of pipeline entities related to SAST configuration.",
"type": {
"kind": "LIST",
"name": null,
@ -64537,7 +64537,7 @@
},
{
"name": "analyzers",
"description": "List of analyzers and related variables for the SAST configuration",
"description": "List of analyzers and related variables for the SAST configuration.",
"type": {
"kind": "LIST",
"name": null,

View File

@ -3183,11 +3183,11 @@ Represents an analyzer entity in SAST CI configuration.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String | Analyzer description that is displayed on the form |
| `enabled` | Boolean | Indicates whether an analyzer is enabled |
| `label` | String | Analyzer label used in the config UI |
| `name` | String | Name of the analyzer |
| `variables` | SastCiConfigurationEntityConnection | List of supported variables |
| `description` | String | Analyzer description that is displayed on the form. |
| `enabled` | Boolean | Indicates whether an analyzer is enabled. |
| `label` | String | Analyzer label used in the config UI. |
| `name` | String | Name of the analyzer. |
| `variables` | SastCiConfigurationEntityConnection | List of supported variables. |
### SastCiConfigurationEntity

View File

@ -4,7 +4,7 @@ group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Approval Rules **(STARTER)**
# Approval Rules development guide
This document explains the backend design and flow of all related functionality
about [merge request approval rules](../user/project/merge_requests/merge_request_approvals.md).

View File

@ -622,13 +622,6 @@ dropped and users get
To help avoid abuse, project and group imports, exports, and export downloads are rate limited. See [Project import/export rate limits](../../user/project/settings/import_export.md#rate-limits) and [Group import/export rate limits](../../user/group/settings/import_export.md#rate-limits) for details.
GitLab.com Import/Export Rate Limits are set to the default except:
| Setting | GitLab.com | Default |
|:-------------------------------------------------|:-----------|:--------|
| Max Project Export requests per minute per user | 1 | 6 |
| Max Group Export requests per minute per user | 1 | 6 |
### Non-configurable limits
See [non-configurable limits](../../security/rate_limits.md#non-configurable-limits) for information on

View File

@ -11,10 +11,8 @@ NOTE:
Project access tokens are supported for self-managed instances on Core and above. They are also supported on GitLab.com Bronze and above (excluding [trial licenses](https://about.gitlab.com/free-trial/)).
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2587) in GitLab 13.0.
> - It was [deployed](https://gitlab.com/groups/gitlab-org/-/epics/2587) behind a feature flag, disabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/218722) in GitLab 13.3.
> - It's recommended for production use.
> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in 13.5 for paid groups only.
> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5 for paid groups only.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.

View File

@ -5,9 +5,10 @@ module Gitlab
module Template
module Finders
class GlobalTemplateFinder < BaseTemplateFinder
def initialize(base_dir, extension, categories = {}, excluded_patterns: [])
def initialize(base_dir, extension, categories = {}, include_categories_for_file = {}, excluded_patterns: [])
@categories = categories
@extension = extension
@include_categories_for_file = include_categories_for_file
@excluded_patterns = excluded_patterns
super(base_dir)
@ -47,7 +48,9 @@ module Gitlab
end
def select_directory(file_name)
@categories.keys.find do |category|
categories = @categories
categories.merge!(@include_categories_for_file[file_name]) if @include_categories_for_file[file_name].present?
categories.keys.find do |category|
File.exist?(File.join(category_directory(category), file_name))
end
end

View File

@ -25,6 +25,12 @@ module Gitlab
}
end
def include_categories_for_file
{
"SAST#{self.extension}" => { 'Security' => 'Security' }
}
end
def excluded_patterns
strong_memoize(:excluded_patterns) do
BASE_EXCLUDED_PATTERNS + additional_excluded_patterns
@ -41,7 +47,11 @@ module Gitlab
def finder(project = nil)
Gitlab::Template::Finders::GlobalTemplateFinder.new(
self.base_dir, self.extension, self.categories, excluded_patterns: self.excluded_patterns
self.base_dir,
self.extension,
self.categories,
self.include_categories_for_file,
excluded_patterns: self.excluded_patterns
)
end
end

View File

@ -0,0 +1,170 @@
# frozen_string_literal: true
module Security
module CiConfiguration
class SastBuildActions
SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs'
def initialize(auto_devops_enabled, params, existing_gitlab_ci_content)
@auto_devops_enabled = auto_devops_enabled
@variables = variables(params)
@existing_gitlab_ci_content = existing_gitlab_ci_content || {}
@default_sast_values = default_sast_values(params)
@default_values_overwritten = false
end
def generate
action = @existing_gitlab_ci_content.present? ? 'update' : 'create'
update_existing_content!
[{ action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }]
end
private
def variables(params)
# This early return is necessary for supporting REST API.
# Will be removed during the implementation of
# https://gitlab.com/gitlab-org/gitlab/-/issues/246737
return params unless params['global'].present?
collect_values(params, 'value')
end
def default_sast_values(params)
collect_values(params, 'defaultValue')
end
def collect_values(config, key)
global_variables = config['global']&.to_h { |k| [k['field'], k[key]] } || {}
pipeline_variables = config['pipeline']&.to_h { |k| [k['field'], k[key]] } || {}
analyzer_variables = collect_analyzer_values(config, key)
global_variables.merge!(pipeline_variables).merge!(analyzer_variables)
end
def collect_analyzer_values(config, key)
analyzer_variables = analyzer_variables_for(config, key)
analyzer_variables['SAST_EXCLUDED_ANALYZERS'] = if key == 'value'
config['analyzers']
&.reject {|a| a['enabled'] }
&.collect {|a| a['name'] }
&.sort
&.join(', ')
else
''
end
analyzer_variables
end
def analyzer_variables_for(config, key)
config['analyzers']
&.select {|a| a['enabled'] && a['variables'] }
&.flat_map {|a| a['variables'] }
&.collect {|v| [v['field'], v[key]] }.to_h
end
def update_existing_content!
@existing_gitlab_ci_content['stages'] = set_stages
@existing_gitlab_ci_content['variables'] = set_variables(global_variables, @existing_gitlab_ci_content)
@existing_gitlab_ci_content['sast'] = set_sast_block
@existing_gitlab_ci_content['include'] = set_includes
@existing_gitlab_ci_content.select! { |k, v| v.present? }
@existing_gitlab_ci_content['sast'].select! { |k, v| v.present? }
end
def set_includes
includes = @existing_gitlab_ci_content['include'] || []
includes = includes.is_a?(Array) ? includes : [includes]
includes << { 'template' => template }
includes.uniq
end
def set_stages
existing_stages = @existing_gitlab_ci_content['stages'] || []
base_stages = @auto_devops_enabled ? auto_devops_stages : ['test']
(existing_stages + base_stages + [sast_stage]).uniq
end
def auto_devops_stages
auto_devops_template = YAML.safe_load( Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content )
auto_devops_template['stages']
end
def sast_stage
@variables['stage'].presence ? @variables['stage'] : 'test'
end
def set_variables(variables, hash_to_update = {})
hash_to_update['variables'] ||= {}
variables.each do |key|
if @variables[key].present? && @variables[key].to_s != @default_sast_values[key].to_s
hash_to_update['variables'][key] = @variables[key]
@default_values_overwritten = true
else
hash_to_update['variables'].delete(key)
end
end
hash_to_update['variables']
end
def set_sast_block
sast_content = @existing_gitlab_ci_content['sast'] || {}
sast_content['variables'] = set_variables(sast_variables)
sast_content['stage'] = sast_stage
sast_content.select { |k, v| v.present? }
end
def prepare_existing_content
content = @existing_gitlab_ci_content.to_yaml
content = remove_document_delimeter(content)
content.prepend(sast_comment)
end
def remove_document_delimeter(content)
content.gsub(/^---\n/, '')
end
def sast_comment
<<~YAML
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
YAML
end
def template
return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
'Security/SAST.gitlab-ci.yml'
end
def global_variables
%w(
SECURE_ANALYZERS_PREFIX
)
end
def sast_variables
%w(
SAST_ANALYZER_IMAGE_TAG
SAST_EXCLUDED_PATHS
SEARCH_MAX_DEPTH
SAST_EXCLUDED_ANALYZERS
SAST_BRAKEMAN_LEVEL
SAST_BANDIT_EXCLUDED_PATHS
SAST_FLAWFINDER_LEVEL
SAST_GOSEC_LEVEL
)
end
end
end
end

View File

@ -15899,9 +15899,6 @@ msgstr ""
msgid "Issues closed"
msgstr ""
msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
msgstr ""
msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
@ -25494,9 +25491,6 @@ msgstr ""
msgid "Select target branch"
msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
msgid "Select the custom project template source group."
msgstr ""
@ -25779,6 +25773,9 @@ msgstr ""
msgid "Set target branch to %{branch_name}."
msgstr ""
msgid "Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one."
msgstr ""
msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}."
msgstr ""
@ -31911,6 +31908,9 @@ msgstr ""
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
msgid "When merge requests and commits in the default branch close, any issues they reference also close."
msgstr ""
msgid "When this merge request is accepted"
msgid_plural "When these merge requests are accepted"
msgstr[0] ""

View File

@ -34,5 +34,15 @@ FactoryBot.define do
size { file.size }
end
trait :codequality_report do
file_type { :code_quality }
size { 2.megabytes }
after(:build) do |artifact, _evaluator|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/pipeline_artifacts/code_quality.json'), 'application/json')
end
end
end
end

View File

@ -163,6 +163,12 @@ FactoryBot.define do
end
end
trait :with_codequality_report_artifact do
after(:build) do |pipeline, evaluator|
pipeline.pipeline_artifacts << build(:ci_pipeline_artifact, :codequality_report, pipeline: pipeline, project: pipeline.project)
end
end
trait :with_terraform_reports do
status { :success }

View File

@ -0,0 +1,16 @@
{
"files": {
"demo.rb": [
{
"line": 5,
"description": "Method `new_array` has 8 arguments (exceeds 4 allowed). Consider refactoring.",
"severity": "major"
},
{
"line": 5,
"description": "Avoid parameter lists longer than 5 parameters. [8/5]",
"severity": "minor"
}
]
}
}

View File

@ -20,6 +20,10 @@ describe('~/api/api_utils.js', () => {
);
});
it('ensures the URL is prefixed with a /', () => {
expect(apiUtils.buildApiUrl('api/:version/projects/:id')).toEqual('/api/v7/projects/:id');
});
describe('when gon includes a relative_url_root property', () => {
beforeEach(() => {
window.gon.relative_url_root = '/relative/root';

View File

@ -26,9 +26,10 @@ exports[`Design management list item component with notes renders item with mult
<img
alt="test"
class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img"
class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
data-qa-filename="test"
data-qa-selector="design_image"
data-testid="design-img-1"
src=""
/>
</gl-intersection-observer-stub>
@ -43,6 +44,8 @@ exports[`Design management list item component with notes renders item with mult
<span
class="gl-font-weight-bold str-truncated-100"
data-qa-selector="design_file_name"
data-testid="design-img-filename-1"
title="test"
>
test
</span>
@ -100,9 +103,10 @@ exports[`Design management list item component with notes renders item with sing
<img
alt="test"
class="gl-display-block gl-mx-auto gl-max-w-full mh-100 design-img"
class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
data-qa-filename="test"
data-qa-selector="design_image"
data-testid="design-img-1"
src=""
/>
</gl-intersection-observer-stub>
@ -117,6 +121,8 @@ exports[`Design management list item component with notes renders item with sing
<span
class="gl-font-weight-bold str-truncated-100"
data-qa-selector="design_file_name"
data-testid="design-img-filename-1"
title="test"
>
test
</span>

View File

@ -1,5 +1,6 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import VueRouter from 'vue-router';
import Item from '~/design_management/components/list/item.vue';
@ -17,8 +18,11 @@ const DESIGN_VERSION_EVENT = {
describe('Design management list item component', () => {
let wrapper;
const imgId = 1;
const imgFilename = 'test';
const findDesignEvent = () => wrapper.find('[data-testid="designEvent"]');
const findDesignEvent = () => wrapper.findByTestId('design-event');
const findImgFilename = (id = imgId) => wrapper.findByTestId(`design-img-filename-${id}`);
const findEventIcon = () => findDesignEvent().find(GlIcon);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
@ -28,25 +32,27 @@ describe('Design management list item component', () => {
isUploading = false,
imageLoading = false,
} = {}) {
wrapper = shallowMount(Item, {
localVue,
router,
propsData: {
id: 1,
filename: 'test',
image: 'http://via.placeholder.com/300',
isUploading,
event,
notesCount,
updatedAt: '01-01-2019',
},
data() {
return {
imageLoading,
};
},
stubs: ['router-link'],
});
wrapper = extendedWrapper(
shallowMount(Item, {
localVue,
router,
propsData: {
id: imgId,
filename: imgFilename,
image: 'http://via.placeholder.com/300',
isUploading,
event,
notesCount,
updatedAt: '01-01-2019',
},
data() {
return {
imageLoading,
};
},
stubs: ['router-link'],
}),
);
}
afterEach(() => {
@ -75,6 +81,10 @@ describe('Design management list item component', () => {
return wrapper.vm.$nextTick();
});
it('renders a tooltip', () => {
expect(findImgFilename().attributes('title')).toEqual(imgFilename);
});
describe('before image is loaded', () => {
it('renders loading spinner', () => {
expect(wrapper.find(GlLoadingIcon)).toExist();

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Types::CiConfiguration::Sast::AnalyzersEntityInputType do
it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntityInput') }
it { expect(described_class.arguments.keys).to match_array(%w[enabled name variables]) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do
let(:fields) { %i[name label enabled description variables] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Types::CiConfiguration::Sast::EntityInputType do
it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntityInput') }
it { expect(described_class.arguments.keys).to match_array(%w[field defaultValue value]) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do
let(:fields) { %i[field label description type options default_value value size] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Types::CiConfiguration::Sast::InputType do
it { expect(described_class.graphql_name).to eq('SastCiConfigurationInput') }
it { expect(described_class.arguments.keys).to match_array(%w[global pipeline analyzers]) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do
let(:fields) { %i[label value] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfiguration'] do
let(:fields) { %i[global pipeline analyzers] }
it { expect(described_class.graphql_name).to eq('SastCiConfiguration') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::CiConfiguration::Sast::UiComponentSizeEnum do
specify { expect(described_class.graphql_name).to eq('SastUiComponentSize') }
it 'exposes all sizes of ui components' do
expect(described_class.values.keys).to include(*%w[SMALL MEDIUM LARGE])
end
end

View File

@ -31,12 +31,171 @@ RSpec.describe GitlabSchema.types['Project'] do
container_expiration_policy service_desk_enabled service_desk_address
issue_status_counts terraform_states alert_management_integrations
container_repositories container_repositories_count
pipeline_analytics squash_read_only
pipeline_analytics squash_read_only sast_ci_configuration
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
describe 'sast_ci_configuration' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
before do
stub_licensed_features(security_dashboard: true)
project.add_developer(user)
allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_content)
end
include_context 'read ci configuration for sast enabled project'
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
sastCiConfiguration {
global {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
size
}
}
pipeline {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
size
}
}
analyzers {
nodes {
name
label
enabled
}
}
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
it "returns the project's sast configuration for global variables" do
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first
expect(secure_analyzers_prefix['type']).to eq('string')
expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX')
expect(secure_analyzers_prefix['label']).to eq('Image prefix')
expect(secure_analyzers_prefix['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(secure_analyzers_prefix['value']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(secure_analyzers_prefix['size']).to eq('LARGE')
expect(secure_analyzers_prefix['options']).to be_nil
end
it "returns the project's sast configuration for pipeline variables" do
pipeline_stage = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first
expect(pipeline_stage['type']).to eq('string')
expect(pipeline_stage['field']).to eq('stage')
expect(pipeline_stage['label']).to eq('Stage')
expect(pipeline_stage['defaultValue']).to eq('test')
expect(pipeline_stage['value']).to eq('test')
expect(pipeline_stage['size']).to eq('MEDIUM')
end
it "returns the project's sast configuration for analyzer variables" do
analyzer = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first
expect(analyzer['name']).to eq('brakeman')
expect(analyzer['label']).to eq('Brakeman')
expect(analyzer['enabled']).to eq(true)
end
context "with guest user" do
before do
project.add_guest(user)
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
it "returns no configuration" do
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration')
expect(secure_analyzers_prefix).to be_nil
end
end
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
context 'when repository is accessible by everyone' do
it "returns the project's sast configuration for global variables" do
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first
expect(secure_analyzers_prefix['type']).to eq('string')
expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX')
end
end
end
end
context "with non-member user" do
before do
project.team.truncate
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
it "returns no configuration" do
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration')
expect(secure_analyzers_prefix).to be_nil
end
end
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
context 'when repository is accessible by everyone' do
it "returns the project's sast configuration for global variables" do
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first
expect(secure_analyzers_prefix['type']).to eq('string')
expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX')
end
end
context 'when repository is accessible only by team members' do
it "returns no configuration" do
project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED,
builds_access_level: ProjectFeature::DISABLED,
repository_access_level: ProjectFeature::PRIVATE)
secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration')
expect(secure_analyzers_prefix).to be_nil
end
end
end
end
end
describe 'issue field' do
subject { described_class.fields['issue'] }

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'validate puma' do
subject do
load Rails.root.join('config/initializers/validate_puma.rb')
end
before do
stub_env('PUMA_SKIP_CLUSTER_VALIDATION', skip_validation)
stub_const('Puma', double)
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
allow(Puma).to receive_message_chain(:cli_config, :options).and_return(workers: workers)
end
context 'for .com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
context 'when worker count is 0' do
let(:workers) { 0 }
context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do
let(:skip_validation) { true }
specify { expect { subject }.to raise_error(String) }
end
context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do
let(:skip_validation) { false }
specify { expect { subject }.to raise_error(String) }
end
end
context 'when worker count is > 0' do
let(:workers) { 2 }
context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do
let(:skip_validation) { true }
specify { expect { subject }.not_to raise_error }
end
context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do
let(:skip_validation) { false }
specify { expect { subject }.not_to raise_error }
end
end
end
context 'for other environments' do
before do
allow(Gitlab).to receive(:com?).and_return(false)
end
context 'when worker count is 0' do
let(:workers) { 0 }
context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do
let(:skip_validation) { true }
specify { expect { subject }.not_to raise_error }
end
context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do
let(:skip_validation) { false }
specify { expect { subject }.to raise_error(String) }
end
end
context 'when worker count is > 0' do
let(:workers) { 2 }
context 'and PUMA_SKIP_CLUSTER_VALIDATION is true' do
let(:skip_validation) { true }
specify { expect { subject }.not_to raise_error }
end
context 'and PUMA_SKIP_CLUSTER_VALIDATION is false' do
let(:skip_validation) { false }
specify { expect { subject }.not_to raise_error }
end
end
end
end

View File

@ -15,9 +15,19 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
FileUtils.rm_rf(base_dir)
end
subject(:finder) { described_class.new(base_dir, '', { 'General' => '', 'Bar' => 'Bar' }, excluded_patterns: excluded_patterns) }
subject(:finder) do
described_class.new(base_dir, '',
{ 'General' => '', 'Bar' => 'Bar' },
include_categories_for_file,
excluded_patterns: excluded_patterns)
end
let(:excluded_patterns) { [] }
let(:include_categories_for_file) do
{
"SAST" => { "Security" => "Security" }
}
end
describe '.find' do
context 'with a non-prefixed General template' do
@ -60,6 +70,7 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
context 'with a prefixed template' do
before do
create_template!('Bar/test-template')
create_template!('Security/SAST')
end
it 'finds the template with a prefix' do
@ -76,6 +87,16 @@ RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
expect { finder.find('../foo') }.to raise_error(/Invalid path/)
end
context 'with include_categories_for_file being present' do
it 'finds the template with a prefix' do
expect(finder.find('SAST')).to be_present
end
it 'does not find any template which is missing in include_categories_for_file' do
expect(finder.find('DAST')).to be_nil
end
end
context 'while listed as an exclusion' do
let(:excluded_patterns) { [%r{^Bar/test-template$}] }

View File

@ -0,0 +1,539 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SastBuildActions do
let(:default_sast_values) do
{ 'global' =>
[
{ 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'registry.gitlab.com/gitlab-org/security-products/analyzers' }
],
'pipeline' =>
[
{ 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'test' },
{ 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 4 },
{ 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 },
{ 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec, test, tests, tmp' }
] }
end
let(:params) do
{ 'global' =>
[
{ 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'new_registry' }
],
'pipeline' =>
[
{ 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'security' },
{ 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 1 },
{ 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 },
{ 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec,docs' }
] }
end
let(:params_with_analyzer_info) do
params.merge( { 'analyzers' =>
[
{
'name' => "bandit",
'enabled' => false
},
{
'name' => "brakeman",
'enabled' => true,
'variables' => [
{ 'field' => "SAST_BRAKEMAN_LEVEL",
'defaultValue' => "1",
'value' => "2" }
]
},
{
'name' => "flawfinder",
'enabled' => true,
'variables' => [
{ 'field' => "SAST_FLAWFINDER_LEVEL",
'defaultValue' => "1",
'value' => "1" }
]
}
] }
)
end
let(:params_with_all_analyzers_enabled) do
params.merge( { 'analyzers' =>
[
{
'name' => "flawfinder",
'enabled' => true
},
{
'name' => "brakeman",
'enabled' => true
}
] }
)
end
context 'with existing .gitlab-ci.yml' do
let(:auto_devops_enabled) { false }
context 'sast has not been included' do
context 'template includes are array' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_template_array_without_sast }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_two_includes)
end
end
context 'template include is not an array' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_without_sast }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_two_includes)
end
it 'reports defaults have been overwritten' do
expect(result.first[:default_values_overwritten]).to eq(true)
end
end
end
context 'sast template include is not an array' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_all_params)
end
end
context 'with default values' do
let(:params) { default_sast_values }
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
end
it 'reports defaults have not been overwritten' do
expect(result.first[:default_values_overwritten]).to eq(false)
end
context 'analyzer section' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage }
subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
end
context 'analyzers are disabled' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage }
subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate }
it 'writes SAST_EXCLUDED_ANALYZERS' do
stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'bandit, brakeman, flawfinder')
expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
end
end
context 'all analyzers are enabled' do
let(:gitlab_ci_content) { existing_gitlab_ci_and_single_template_with_sast_and_default_stage }
subject(:result) { described_class.new(auto_devops_enabled, params_with_all_analyzers_enabled, gitlab_ci_content).generate }
it 'does not write SAST_DEFAULT_ANALYZERS or SAST_EXCLUDED_ANALYZERS' do
stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'brakeman, flawfinder')
expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
end
end
end
end
context 'with update stage and SEARCH_MAX_DEPTH and set SECURE_ANALYZERS_PREFIX to default' do
let(:params) do
{ 'global' =>
[
{ 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => 'registry.gitlab.com/gitlab-org/security-products/analyzers' }
],
'pipeline' =>
[
{ 'field' => 'stage', 'defaultValue' => 'test', 'value' => 'brand_new_stage' },
{ 'field' => 'SEARCH_MAX_DEPTH', 'defaultValue' => 4, 'value' => 5 },
{ 'field' => 'SAST_ANALYZER_IMAGE_TAG', 'defaultValue' => 2, 'value' => 2 },
{ 'field' => 'SAST_EXCLUDED_PATHS', 'defaultValue' => 'spec, test, tests, tmp', 'value' => 'spec,docs' }
] }
end
let(:gitlab_ci_content) { existing_gitlab_ci }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_updated_stage)
end
end
context 'with no existing variables' do
let(:gitlab_ci_content) { existing_gitlab_ci_with_no_variables }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_variable_section_added)
end
end
context 'with no existing sast config' do
let(:gitlab_ci_content) { existing_gitlab_ci_with_no_sast_section }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_sast_section_added)
end
end
context 'with no existing sast variables' do
let(:gitlab_ci_content) { existing_gitlab_ci_with_no_sast_variables }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:action]).to eq('update')
expect(result.first[:content]).to eq(sast_yaml_sast_variables_section_added)
end
end
def existing_gitlab_ci_and_template_array_without_sast
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" },
"sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" },
"include" => [{ "template" => "existing.yml" }] }
end
def existing_gitlab_ci_and_single_template_with_sast_and_default_stage
{ "stages" => %w(test),
"variables" => { "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" },
"sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "test" },
"include" => { "template" => "Security/SAST.gitlab-ci.yml" } }
end
def existing_gitlab_ci_and_single_template_without_sast
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" },
"sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" },
"include" => { "template" => "existing.yml" } }
end
def existing_gitlab_ci_with_no_variables
{ "stages" => %w(test security),
"sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" },
"include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] }
end
def existing_gitlab_ci_with_no_sast_section
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" },
"include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] }
end
def existing_gitlab_ci_with_no_sast_variables
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" },
"sast" => { "stage" => "security" },
"include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] }
end
def existing_gitlab_ci
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "bad_prefix" },
"sast" => { "variables" => { "SAST_ANALYZER_IMAGE_TAG" => 2, "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" },
"include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] }
end
end
context 'with no .gitlab-ci.yml' do
let(:gitlab_ci_content) { nil }
context 'autodevops disabled' do
let(:auto_devops_enabled) { false }
context 'with one empty parameter' do
let(:params) do
{ 'global' =>
[
{ 'field' => 'SECURE_ANALYZERS_PREFIX', 'defaultValue' => 'registry.gitlab.com/gitlab-org/security-products/analyzers', 'value' => '' }
] }
end
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
end
end
context 'with all parameters' do
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
it 'generates the correct YML' do
expect(result.first[:content]).to eq(sast_yaml_all_params)
end
end
end
context 'with autodevops enabled' do
let(:auto_devops_enabled) { true }
subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
before do
allow_next_instance_of(described_class) do |sast_build_actions|
allow(sast_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages)
end
end
it 'generates the correct YML' do
expect(result.first[:content]).to eq(auto_devops_with_custom_stage)
end
end
end
describe 'Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS' do
subject(:variable) {Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS}
it 'is sorted alphabetically' do
sorted_variable = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS
.split(',')
.map(&:strip)
.sort
.join(', ')
expect(variable).to eq(sorted_variable)
end
end
# stubbing this method allows this spec file to use fast_spec_helper
def fast_auto_devops_stages
auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') )
auto_devops_template['stages']
end
def sast_yaml_with_no_variables_set_but_analyzers
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
sast:
variables:
SAST_EXCLUDED_ANALYZERS: bandit
SAST_BRAKEMAN_LEVEL: '2'
stage: test
include:
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
def sast_yaml_with_no_variables_set
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
sast:
stage: test
include:
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
def sast_yaml_all_params
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
variables:
SECURE_ANALYZERS_PREFIX: new_registry
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
stage: security
include:
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
def auto_devops_with_custom_stage
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- build
- test
- deploy
- review
- dast
- staging
- canary
- production
- incremental rollout 10%
- incremental rollout 25%
- incremental rollout 50%
- incremental rollout 100%
- performance
- cleanup
- security
variables:
SECURE_ANALYZERS_PREFIX: new_registry
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
stage: security
include:
- template: Auto-DevOps.gitlab-ci.yml
CI_YML
end
def sast_yaml_two_includes
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
variables:
RANDOM: make sure this persists
SECURE_ANALYZERS_PREFIX: new_registry
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
stage: security
include:
- template: existing.yml
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
def sast_yaml_variable_section_added
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
stage: security
include:
- template: Security/SAST.gitlab-ci.yml
variables:
SECURE_ANALYZERS_PREFIX: new_registry
CI_YML
end
def sast_yaml_sast_section_added
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
variables:
RANDOM: make sure this persists
SECURE_ANALYZERS_PREFIX: new_registry
include:
- template: Security/SAST.gitlab-ci.yml
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
stage: security
CI_YML
end
def sast_yaml_sast_variables_section_added
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
variables:
RANDOM: make sure this persists
SECURE_ANALYZERS_PREFIX: new_registry
sast:
stage: security
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 1
include:
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
def sast_yaml_updated_stage
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
- brand_new_stage
variables:
RANDOM: make sure this persists
sast:
variables:
SAST_EXCLUDED_PATHS: spec,docs
SEARCH_MAX_DEPTH: 5
stage: brand_new_stage
include:
- template: Security/SAST.gitlab-ci.yml
CI_YML
end
end

View File

@ -76,38 +76,98 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
end
end
describe '.has_code_coverage?' do
subject { Ci::PipelineArtifact.has_code_coverage? }
describe '.has_report?' do
subject(:pipeline_artifact) { Ci::PipelineArtifact.has_report?(file_type) }
context 'when pipeline artifact has a code coverage' do
let!(:pipeline_artifact) { create(:ci_pipeline_artifact) }
context 'when file_type is code_coverage' do
let(:file_type) { :code_coverage }
it 'returns true' do
expect(subject).to be_truthy
context 'when pipeline artifact has a coverage report' do
let!(:pipeline_artifact) { create(:ci_pipeline_artifact) }
it 'returns true' do
expect(pipeline_artifact).to be_truthy
end
end
context 'when pipeline artifact does not have a coverage report' do
it 'returns false' do
expect(pipeline_artifact).to be_falsey
end
end
end
context 'when pipeline artifact does not have a code coverage' do
context 'when file_type is code_quality' do
let(:file_type) { :code_quality }
context 'when pipeline artifact has a quality report' do
let!(:pipeline_artifact) { create(:ci_pipeline_artifact, :codequality_report) }
it 'returns true' do
expect(pipeline_artifact).to be_truthy
end
end
context 'when pipeline artifact does not have a quality report' do
it 'returns false' do
expect(pipeline_artifact).to be_falsey
end
end
end
context 'when file_type is nil' do
let(:file_type) { nil }
it 'returns false' do
expect(subject).to be_falsey
expect(pipeline_artifact).to be_falsey
end
end
end
describe '.find_with_code_coverage' do
subject { Ci::PipelineArtifact.find_with_code_coverage }
describe '.find_by_file_type' do
subject(:pipeline_artifact) { Ci::PipelineArtifact.find_by_file_type(file_type) }
context 'when pipeline artifact has a coverage report' do
let!(:coverage_report) { create(:ci_pipeline_artifact) }
context 'when file_type is code_coverage' do
let(:file_type) { :code_coverage }
it 'returns a pipeline artifact with a code coverage' do
expect(subject.file_type).to eq('code_coverage')
context 'when pipeline artifact has a coverage report' do
let!(:coverage_report) { create(:ci_pipeline_artifact) }
it 'returns a pipeline artifact with a coverage report' do
expect(pipeline_artifact.file_type).to eq('code_coverage')
end
end
context 'when pipeline artifact does not have a coverage report' do
it 'returns nil' do
expect(pipeline_artifact).to be_nil
end
end
end
context 'when pipeline artifact does not have a coverage report' do
context 'when file_type is code_quality' do
let(:file_type) { :code_quality }
context 'when pipeline artifact has a quality report' do
let!(:coverage_report) { create(:ci_pipeline_artifact, :codequality_report) }
it 'returns a pipeline artifact with a quality report' do
expect(pipeline_artifact.file_type).to eq('code_quality')
end
end
context 'when pipeline artifact does not have a quality report' do
it 'returns nil' do
expect(pipeline_artifact).to be_nil
end
end
end
context 'when file_type is nil' do
let(:file_type) { nil }
it 'returns nil' do
expect(subject).to be_nil
expect(pipeline_artifact).to be_nil
end
end
end

View File

@ -3489,6 +3489,54 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
describe '#has_codequality_reports?' do
subject { pipeline.has_codequality_reports? }
context 'when pipeline has a codequality artifact' do
let(:pipeline) { create(:ci_pipeline, :with_codequality_report_artifact, :running, project: project) }
it { expect(subject).to be_truthy }
end
context 'when pipeline does not have a codequality artifact' do
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
it { expect(subject).to be_falsey }
end
end
describe '#can_generate_codequality_reports?' do
subject { pipeline.can_generate_codequality_reports? }
context 'when pipeline has builds with codequality reports' do
before do
create(:ci_build, :codequality_reports, pipeline: pipeline, project: project)
end
context 'when pipeline status is running' do
let(:pipeline) { create(:ci_pipeline, :running, project: project) }
it { expect(subject).to be_falsey }
end
context 'when pipeline status is success' do
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
it { expect(subject).to be_truthy }
end
end
context 'when pipeline does not have builds with codequality reports' do
before do
create(:ci_build, :artifacts, pipeline: pipeline, project: project)
end
let(:pipeline) { create(:ci_pipeline, :success, project: project) }
it { expect(subject).to be_falsey }
end
end
describe '#test_report_summary' do
subject { pipeline.test_report_summary }

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SastParserService do
describe '#configuration' do
include_context 'read ci configuration for sast enabled project'
let(:configuration) { described_class.new(project).configuration }
let(:secure_analyzers_prefix) { configuration['global'][0] }
let(:sast_excluded_paths) { configuration['global'][1] }
let(:sast_analyzer_image_tag) { configuration['global'][2] }
let(:sast_pipeline_stage) { configuration['pipeline'][0] }
let(:sast_search_max_depth) { configuration['pipeline'][1] }
let(:brakeman) { configuration['analyzers'][0] }
let(:bandit) { configuration['analyzers'][1] }
let(:sast_brakeman_level) { brakeman['variables'][0] }
it 'parses the configuration for SAST' do
expect(secure_analyzers_prefix['default_value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(sast_excluded_paths['default_value']).to eql('spec, test, tests, tmp')
expect(sast_analyzer_image_tag['default_value']).to eql('2')
expect(sast_pipeline_stage['default_value']).to eql('test')
expect(sast_search_max_depth['default_value']).to eql('4')
expect(brakeman['enabled']).to be(true)
expect(sast_brakeman_level['default_value']).to eql('1')
end
context 'while populating current values of the entities' do
context 'when .gitlab-ci.yml is present' do
it 'populates the current values from the file' do
allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_content)
expect(secure_analyzers_prefix['value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers2')
expect(sast_excluded_paths['value']).to eql('spec, executables')
expect(sast_analyzer_image_tag['value']).to eql('2')
expect(sast_pipeline_stage['value']).to eql('our_custom_security_stage')
expect(sast_search_max_depth['value']).to eql('8')
expect(brakeman['enabled']).to be(false)
expect(bandit['enabled']).to be(true)
expect(sast_brakeman_level['value']).to eql('2')
end
context 'SAST_DEFAULT_ANALYZERS is set' do
it 'enables analyzers correctly' do
allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_default_analyzers_content)
expect(brakeman['enabled']).to be(false)
expect(bandit['enabled']).to be(true)
end
end
context 'SAST_EXCLUDED_ANALYZERS is set' do
it 'enables analyzers correctly' do
allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_excluded_analyzers_content)
expect(brakeman['enabled']).to be(false)
expect(bandit['enabled']).to be(true)
end
end
end
context 'when .gitlab-ci.yml is absent' do
it 'populates the current values with the default values' do
allow(project.repository).to receive(:blob_data_at).and_return(nil)
expect(secure_analyzers_prefix['value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(sast_excluded_paths['value']).to eql('spec, test, tests, tmp')
expect(sast_analyzer_image_tag['value']).to eql('2')
expect(sast_pipeline_stage['value']).to eql('test')
expect(sast_search_max_depth['value']).to eql('4')
expect(brakeman['enabled']).to be(true)
expect(sast_brakeman_level['value']).to eql('1')
end
end
end
end
end