diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb index e4a3f815396f4de8cf81424522be23572018f4d6..237aff1f052426118841bee7758b39772e17f67b 100644 --- a/app/graphql/mutations/security/ci_configuration/configure_sast.rb +++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb @@ -7,6 +7,11 @@ class ConfigureSast < BaseMutation include FindsProject graphql_name 'ConfigureSast' + description <<~DESC + Configure SAST for a project by enabling SAST in a new or modified + `.gitlab-ci.yml` file in a new branch. The new branch and a URL to + create a Merge Request are a part of the response. + DESC argument :project_path, GraphQL::ID_TYPE, required: true, @@ -16,12 +21,12 @@ class ConfigureSast < BaseMutation required: true, description: 'SAST CI configuration for the project.' - field :status, GraphQL::STRING_TYPE, null: false, - description: 'Status of creating the commit for the supplied SAST CI configuration.' - field :success_path, GraphQL::STRING_TYPE, null: true, description: 'Redirect path to use when the response is successful.' + field :branch, GraphQL::STRING_TYPE, null: true, + description: 'Branch that has the new/modified `.gitlab-ci.yml` file.' + authorize :push_code def resolve(project_path:, configuration:) @@ -35,9 +40,9 @@ def resolve(project_path:, configuration:) def prepare_response(result) { - status: result[:status], - success_path: result[:success_path], - errors: Array(result[:errors]) + branch: result.payload[:branch], + success_path: result.payload[:success_path], + errors: result.errors } end end diff --git a/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb new file mode 100644 index 0000000000000000000000000000000000000000..32ad670edaa9745361a6511a7066b83f85a369ed --- /dev/null +++ b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Mutations + module Security + module CiConfiguration + class ConfigureSecretDetection < BaseMutation + include FindsProject + + graphql_name 'ConfigureSecretDetection' + description <<~DESC + Configure Secret Detection for a project by enabling Secret Detection + in a new or modified `.gitlab-ci.yml` file in a new branch. The new + branch and a URL to create a Merge Request are a part of the + response. + DESC + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Full path of the project.' + + field :success_path, GraphQL::STRING_TYPE, null: true, + description: 'Redirect path to use when the response is successful.' + + field :branch, GraphQL::STRING_TYPE, null: true, + description: 'Branch that has the new/modified `.gitlab-ci.yml` file.' + + authorize :push_code + + def resolve(project_path:) + project = authorized_find!(project_path) + + result = ::Security::CiConfiguration::SecretDetectionCreateService.new(project, current_user).execute + prepare_response(result) + end + + private + + def prepare_response(result) + { + branch: result.payload[:branch], + success_path: result.payload[:success_path], + errors: result.errors + } + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 5a9c7b32debdf1d382a74fd2d0f0922b831ecfcd..a820ef9471945b462c53e3aaaf3d0eabaec624f4 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -16,6 +16,7 @@ class MutationType < BaseObject mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy mount_mutation Mutations::Security::CiConfiguration::ConfigureSast + mount_mutation Mutations::Security::CiConfiguration::ConfigureSecretDetection mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken diff --git a/app/services/security/ci_configuration/base_create_service.rb b/app/services/security/ci_configuration/base_create_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..adb45244adbea019927b1a07313af0dc19a3c7ee --- /dev/null +++ b/app/services/security/ci_configuration/base_create_service.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + class BaseCreateService + attr_reader :branch_name, :current_user, :project + + def initialize(project, current_user) + @project = project + @current_user = current_user + @branch_name = project.repository.next_branch(next_branch) + end + + def execute + project.repository.add_branch(current_user, branch_name, project.default_branch) + + attributes_for_commit = attributes + + result = ::Files::MultiService.new(project, current_user, attributes_for_commit).execute + + return ServiceResponse.error(message: result[:message]) unless result[:status] == :success + + track_event(attributes_for_commit) + ServiceResponse.success(payload: { branch: branch_name, success_path: successful_change_path }) + rescue Gitlab::Git::PreReceiveError => e + ServiceResponse.error(message: e.message) + rescue StandardError + project.repository.rm_branch(current_user, branch_name) if project.repository.branch_exists?(branch_name) + raise + end + + private + + def attributes + { + commit_message: message, + branch_name: branch_name, + start_branch: branch_name, + actions: [action] + } + end + + def existing_gitlab_ci_content + @gitlab_ci_yml ||= project.repository.gitlab_ci_yml_for(project.repository.root_ref_sha) + YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml + end + + def successful_change_path + merge_request_params = { source_branch: branch_name, description: description } + Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params) + end + + def track_event(attributes_for_commit) + action = attributes_for_commit[:actions].first + + Gitlab::Tracking.event( + self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s + ) + end + end + end +end diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb index 8fc3b8d078cc7f24f4a8f258af1de1c1bdb61aff..f495cac18f8153ec9b158af445258bc5c4976c89 100644 --- a/app/services/security/ci_configuration/sast_create_service.rb +++ b/app/services/security/ci_configuration/sast_create_service.rb @@ -2,64 +2,30 @@ module Security module CiConfiguration - class SastCreateService < ::BaseService + class SastCreateService < ::Security::CiConfiguration::BaseCreateService + attr_reader :params + def initialize(project, current_user, params) - @project = project - @current_user = current_user + super(project, current_user) @params = params - @branch_name = @project.repository.next_branch('set-sast-config') - end - - def execute - attributes_for_commit = attributes - result = ::Files::MultiService.new(@project, @current_user, attributes_for_commit).execute - - if result[:status] == :success - result[:success_path] = successful_change_path - track_event(attributes_for_commit) - else - result[:errors] = result[:message] - end - - result - - rescue Gitlab::Git::PreReceiveError => e - { status: :error, errors: e.message } end private - def attributes - actions = Security::CiConfiguration::SastBuildActions.new(@project.auto_devops_enabled?, @params, existing_gitlab_ci_content).generate - - @project.repository.add_branch(@current_user, @branch_name, @project.default_branch) - message = _('Set .gitlab-ci.yml to enable or configure SAST') - - { - commit_message: message, - branch_name: @branch_name, - start_branch: @branch_name, - actions: actions - } + def action + Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content).generate end - def existing_gitlab_ci_content - gitlab_ci_yml = @project.repository.gitlab_ci_yml_for(@project.repository.root_ref_sha) - YAML.safe_load(gitlab_ci_yml) if gitlab_ci_yml + def next_branch + 'set-sast-config' end - def successful_change_path - description = _('Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.') - merge_request_params = { source_branch: @branch_name, description: description } - Gitlab::Routing.url_helpers.project_new_merge_request_url(@project, merge_request: merge_request_params) + def message + _('Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist') end - def track_event(attributes_for_commit) - action = attributes_for_commit[:actions].first - - Gitlab::Tracking.event( - self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s - ) + def description + _('Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.') end end end diff --git a/app/services/security/ci_configuration/sast_parser_service.rb b/app/services/security/ci_configuration/sast_parser_service.rb index a8fe5764d193ec3cade06ecfd47bf22ec603e89c..5220525d552f9604d5514684922e456422444e53 100644 --- a/app/services/security/ci_configuration/sast_parser_service.rb +++ b/app/services/security/ci_configuration/sast_parser_service.rb @@ -74,7 +74,7 @@ def analyzer_enabled?(analyzer_name) def sast_excluded_analyzers strong_memoize(:sast_excluded_analyzers) do - all_analyzers = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS.split(', ') rescue [] + all_analyzers = Security::CiConfiguration::SastBuildAction::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"] diff --git a/app/services/security/ci_configuration/secret_detection_create_service.rb b/app/services/security/ci_configuration/secret_detection_create_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..ff3458d36fcae5d8a16daf87924abe2cc88ea736 --- /dev/null +++ b/app/services/security/ci_configuration/secret_detection_create_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + class SecretDetectionCreateService < ::Security::CiConfiguration::BaseCreateService + private + + def action + Security::CiConfiguration::SecretDetectionBuildAction.new(project.auto_devops_enabled?, existing_gitlab_ci_content).generate + end + + def next_branch + 'set-secret-detection-config' + end + + def message + _('Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist') + end + + def description + _('Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings.') + end + end + end +end diff --git a/changelogs/unreleased/321282-be-graphql-sd-settings.yml b/changelogs/unreleased/321282-be-graphql-sd-settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ab0fa1af0e1ba6e0aa053c0ff615ce117efff5f --- /dev/null +++ b/changelogs/unreleased/321282-be-graphql-sd-settings.yml @@ -0,0 +1,5 @@ +--- +title: Add ConfigureSecretDetection graphql mutation +merge_request: 58230 +author: +type: added diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml index 3c6ef13dd1c07b3d0cc5de83fa3c1753a08ed184..f9929418a3d278714503edf5c61a0e1fa7b3ce2e 100644 --- a/config/known_invalid_graphql_queries.yml +++ b/config/known_invalid_graphql_queries.yml @@ -6,4 +6,3 @@ filenames: - ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql - app/assets/javascripts/repository/queries/blob_info.query.graphql - ee/app/assets/javascripts/security_configuration/graphql/configure_dependency_scanning.mutation.graphql - - ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 197c1dc373f5491817f61b3b17366c76a1ac4163..57a3b55f88f0adf6819fc5ab43573b15741faf3b 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -851,6 +851,10 @@ Input type: `CommitCreateInput` ### `Mutation.configureSast` +Configure SAST for a project by enabling SAST in a new or modified +`.gitlab-ci.yml` file in a new branch. The new branch and a URL to +create a Merge Request are a part of the response. + Input type: `ConfigureSastInput` #### Arguments @@ -865,11 +869,36 @@ Input type: `ConfigureSastInput` | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="mutationconfiguresastbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. | | <a id="mutationconfiguresastclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationconfiguresasterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | -| <a id="mutationconfiguresaststatus"></a>`status` | [`String!`](#string) | Status of creating the commit for the supplied SAST CI configuration. | | <a id="mutationconfiguresastsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. | +### `Mutation.configureSecretDetection` + +Configure Secret Detection for a project by enabling Secret Detection +in a new or modified `.gitlab-ci.yml` file in a new branch. The new +branch and a URL to create a Merge Request are a part of the +response. + +Input type: `ConfigureSecretDetectionInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationconfiguresecretdetectionprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationconfiguresecretdetectionbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. | +| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationconfiguresecretdetectionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| <a id="mutationconfiguresecretdetectionsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. | + ### `Mutation.createAlertIssue` Input type: `CreateAlertIssueInput` diff --git a/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue b/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue index a0a3b1aa2ad28e203936d919739928465d6b508a..b79fdbd2cec32f18c2152cdf982de9330326c015 100644 --- a/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue +++ b/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue @@ -39,7 +39,9 @@ export default { const { data } = await this.$apollo.mutate({ mutation: this.featureSettings.mutation, variables: { - fullPath: this.projectPath, + input: { + projectPath: this.projectPath, + }, }, }); const { errors, successPath } = data[this.featureSettings.type]; diff --git a/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql b/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql index 2fd4b0982df65a1c015bf5d225dac39f14234523..e42a8de64f35a6e83c759b784c4110a2d80eb652 100644 --- a/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql +++ b/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql @@ -1,5 +1,5 @@ -mutation configureSecretDetection($fullPath: ID!) { - configureSecretDetection(fullPath: $fullPath) { +mutation configureSecretDetection($input: ConfigureSecretDetectionInput!) { + configureSecretDetection(input: $input) { successPath errors } diff --git a/ee/app/assets/javascripts/security_configuration/index.js b/ee/app/assets/javascripts/security_configuration/index.js index ab15b0309d17993dcf751a4dd377d3385172301b..93d7659ff89403d86e1e51033ecd3660b91b5cd5 100644 --- a/ee/app/assets/javascripts/security_configuration/index.js +++ b/ee/app/assets/javascripts/security_configuration/index.js @@ -19,6 +19,7 @@ export const initSecurityConfiguration = (el) => { dependencyScanningHelpPath, toggleAutofixSettingEndpoint, gitlabCiHistoryPath, + projectPath, } = el.dataset; return new Vue({ @@ -26,6 +27,9 @@ export const initSecurityConfiguration = (el) => { components: { SecurityConfigurationApp, }, + provide: { + projectPath, + }, render(createElement) { return createElement(SecurityConfigurationApp, { props: { diff --git a/ee/app/views/projects/security/configuration/show.html.haml b/ee/app/views/projects/security/configuration/show.html.haml index cf849bff30a777572124cfd8b2644a78eb14694d..dcf062a5e04bfb93e11b4d4c6b2950ca4b561f26 100644 --- a/ee/app/views/projects/security/configuration/show.html.haml +++ b/ee/app/views/projects/security/configuration/show.html.haml @@ -5,6 +5,7 @@ = render_ce 'projects/security/configuration/show' - else #js-security-configuration{ data: { **@configuration.to_html_data_attribute, + project_path: @project.full_path, auto_fix_help_path: '/', toggle_autofix_setting_endpoint: 'configuration/auto_fix', container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'), diff --git a/lib/security/ci_configuration/base_build_action.rb b/lib/security/ci_configuration/base_build_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..b169d780cad5de35ac9e725c393a8e516cb29ce3 --- /dev/null +++ b/lib/security/ci_configuration/base_build_action.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + class BaseBuildAction + def initialize(auto_devops_enabled, existing_gitlab_ci_content) + @auto_devops_enabled = auto_devops_enabled + @existing_gitlab_ci_content = existing_gitlab_ci_content || {} + 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 generate_includes + includes = @existing_gitlab_ci_content['include'] || [] + includes = Array.wrap(includes) + includes << { 'template' => template } + includes.uniq + end + + def prepare_existing_content + content = @existing_gitlab_ci_content.to_yaml + content = remove_document_delimiter(content) + + content.prepend(comment) + end + + def remove_document_delimiter(content) + content.gsub(/^---\n/, '') + end + + def comment + <<~YAML + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-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 + end + end +end diff --git a/lib/security/ci_configuration/sast_build_actions.rb b/lib/security/ci_configuration/sast_build_action.rb similarity index 76% rename from lib/security/ci_configuration/sast_build_actions.rb rename to lib/security/ci_configuration/sast_build_action.rb index b2d684bc1e1204564cd00429880f4d60ef55136e..c319fc1637db162a0481e95f4af75c6c2d83aa06 100644 --- a/lib/security/ci_configuration/sast_build_actions.rb +++ b/lib/security/ci_configuration/sast_build_action.rb @@ -2,25 +2,16 @@ module Security module CiConfiguration - class SastBuildActions + class SastBuildAction < BaseBuildAction 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 + super(auto_devops_enabled, existing_gitlab_ci_content) @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) @@ -71,19 +62,12 @@ 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['include'] = generate_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'] @@ -121,26 +105,6 @@ def set_sast_block 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 diff --git a/lib/security/ci_configuration/secret_detection_build_action.rb b/lib/security/ci_configuration/secret_detection_build_action.rb new file mode 100644 index 0000000000000000000000000000000000000000..5d513bf55470f708ffb2e10cf56114b776eb1a9d --- /dev/null +++ b/lib/security/ci_configuration/secret_detection_build_action.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Security + module CiConfiguration + class SecretDetectionBuildAction < BaseBuildAction + private + + def update_existing_content! + @existing_gitlab_ci_content['include'] = generate_includes + end + + def template + return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled + + 'Security/Secret-Detection.gitlab-ci.yml' + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e992addf7afb2de04cb945ddf3f0d060fdc0c9bb..d54afe256f87eb6ccd5c094447f5473966145cb8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8305,6 +8305,18 @@ msgstr "" msgid "Configure Prometheus" msgstr "" +msgid "Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings." +msgstr "" + +msgid "Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist" +msgstr "" + +msgid "Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings." +msgstr "" + +msgid "Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist" +msgstr "" + msgid "Configure Tracing" msgstr "" @@ -29089,12 +29101,6 @@ msgstr "" msgid "Set %{epic_ref} as the parent epic." msgstr "" -msgid "Set .gitlab-ci.yml to enable or configure SAST" -msgstr "" - -msgid "Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings." -msgstr "" - msgid "Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}" msgstr "" diff --git a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb index ed03a1cb9068e10386e96be5f2dff93fd19b4c87..7c3b552480f32c2411d2652c73bc4c684b7337cb 100644 --- a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb +++ b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb @@ -3,118 +3,11 @@ require 'spec_helper' RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do - subject(:mutation) { described_class.new(object: nil, context: context, field: nil) } + include GraphqlHelpers - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:user) { create(:user) } + let(:service) { ::Security::CiConfiguration::SastCreateService } - let_it_be(:service_result_json) do - { - status: "success", - success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?", - errors: nil - } - end + subject { resolve(described_class, args: { project_path: project.full_path, configuration: {} }, ctx: { current_user: user }) } - let_it_be(:service_error_result_json) do - { - status: "error", - success_path: nil, - errors: %w(error1 error2) - } - end - - let(:context) do - GraphQL::Query::Context.new( - query: OpenStruct.new(schema: nil), - values: { current_user: user }, - object: nil - ) - end - - specify { expect(described_class).to require_graphql_authorizations(:push_code) } - - describe '#resolve' do - subject { mutation.resolve(project_path: project.full_path, configuration: {}) } - - let(:result) { subject } - - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - - context 'when user does not have enough permissions' do - before do - project.add_guest(user) - end - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when user is a maintainer of a different project' do - before do - create(:project_empty_repo).add_maintainer(user) - end - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when the user does not have permission to create a new branch' do - before_all do - project.add_developer(user) - end - - let(:error_message) { 'You are not allowed to create protected branches on this project.' } - - it 'returns an array of errors' do - allow_next_instance_of(::Files::MultiService) do |multi_service| - allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}")) - end - - expect(result).to match( - status: :error, - success_path: nil, - errors: match_array([error_message]) - ) - end - end - - context 'when the user can create a merge request' do - before_all do - project.add_developer(user) - end - - context 'when service successfully generates a path to create a new merge request' do - it 'returns a success path' do - allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| - allow(service).to receive(:execute).and_return(service_result_json) - end - - expect(result).to match( - status: 'success', - success_path: service_result_json[:success_path], - errors: [] - ) - end - end - - context 'when service can not generate any path to create a new merge request' do - it 'returns an array of errors' do - allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| - allow(service).to receive(:execute).and_return(service_error_result_json) - end - - expect(result).to match( - status: 'error', - success_path: be_nil, - errors: match_array(service_error_result_json[:errors]) - ) - end - end - end - end + include_examples 'graphql mutations security ci configuration' end diff --git a/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5b4a7d5918c09effa092ec9b96d2d52f886c5fe5 --- /dev/null +++ b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Security::CiConfiguration::ConfigureSecretDetection do + include GraphqlHelpers + + let(:service) { ::Security::CiConfiguration::SecretDetectionCreateService } + + subject { resolve(described_class, args: { project_path: project.full_path }, ctx: { current_user: user }) } + + include_examples 'graphql mutations security ci configuration' +end diff --git a/spec/lib/security/ci_configuration/sast_build_actions_spec.rb b/spec/lib/security/ci_configuration/sast_build_action_spec.rb similarity index 80% rename from spec/lib/security/ci_configuration/sast_build_actions_spec.rb rename to spec/lib/security/ci_configuration/sast_build_action_spec.rb index c8f9430eff9404949217c559d8aed83ee86a1a76..6a1ea68fdd6d305ce0fbae0acc7972097352776f 100644 --- a/spec/lib/security/ci_configuration/sast_build_actions_spec.rb +++ b/spec/lib/security/ci_configuration/sast_build_action_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::CiConfiguration::SastBuildActions do +RSpec.describe Security::CiConfiguration::SastBuildAction do let(:default_sast_values) do { 'global' => [ @@ -85,8 +85,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_two_includes) end end @@ -96,12 +96,12 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_two_includes) end it 'reports defaults have been overwritten' do - expect(result.first[:default_values_overwritten]).to eq(true) + expect(result[:default_values_overwritten]).to eq(true) end end end @@ -112,8 +112,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_all_params) end end @@ -124,11 +124,11 @@ 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) + expect(result[: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) + expect(result[:default_values_overwritten]).to eq(false) end context 'analyzer section' do @@ -137,7 +137,7 @@ 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) + expect(result[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers) end context 'analyzers are disabled' do @@ -146,9 +146,9 @@ 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') + stub_const('Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS', 'bandit, brakeman, flawfinder') - expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers) + expect(result[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers) end end @@ -158,9 +158,9 @@ 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') + stub_const('Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS', 'brakeman, flawfinder') - expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set) + expect(result[:content]).to eq(sast_yaml_with_no_variables_set) end end end @@ -186,8 +186,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_updated_stage) end end @@ -197,8 +197,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_variable_section_added) end end @@ -208,8 +208,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_sast_section_added) end end @@ -219,8 +219,8 @@ 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) + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(sast_yaml_sast_variables_section_added) end end @@ -289,7 +289,7 @@ def 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[:content]).to eq(sast_yaml_with_no_variables_set) + expect(result[:content]).to eq(sast_yaml_with_no_variables_set) end end @@ -297,7 +297,7 @@ def 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[:content]).to eq(sast_yaml_all_params) + expect(result[:content]).to eq(sast_yaml_all_params) end end end @@ -308,22 +308,22 @@ def existing_gitlab_ci 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) + allow_next_instance_of(described_class) do |sast_build_action| + allow(sast_build_action).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) + expect(result[: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} + describe 'Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS' do + subject(:variable) {Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS} it 'is sorted alphabetically' do - sorted_variable = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS + sorted_variable = Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS .split(',') .map(&:strip) .sort @@ -342,7 +342,8 @@ def fast_auto_devops_stages 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -360,7 +361,8 @@ def sast_yaml_with_no_variables_set_but_analyzers 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -375,7 +377,8 @@ def sast_yaml_with_no_variables_set 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -396,7 +399,8 @@ def sast_yaml_all_params 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -430,7 +434,8 @@ def auto_devops_with_custom_stage 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -453,7 +458,8 @@ def sast_yaml_two_includes 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -474,7 +480,8 @@ def sast_yaml_variable_section_added 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -496,7 +503,8 @@ def sast_yaml_sast_section_added 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: @@ -518,7 +526,8 @@ def sast_yaml_sast_variables_section_added 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 + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables stages: diff --git a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..31854fcf3a731e22d9fc1d0893ca4ba50a2dd864 --- /dev/null +++ b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do + subject(:result) { described_class.new(auto_devops_enabled, gitlab_ci_content).generate } + + let(:params) { {} } + + context 'with existing .gitlab-ci.yml' do + let(:auto_devops_enabled) { false } + + context 'secret_detection has not been included' do + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-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 + include: + - template: existing.yml + - template: Security/Secret-Detection.gitlab-ci.yml + CI_YML + end + + context 'template includes are an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => [{ "template" => "existing.yml" }] } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'template include is not an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => { "template" => "existing.yml" } } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + end + + context 'secret_detection has been included' do + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-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 + variables: + RANDOM: make sure this persists + include: + - template: Security/Secret-Detection.gitlab-ci.yml + CI_YML + end + + context 'secret_detection template include are an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => [{ "template" => "Security/Secret-Detection.gitlab-ci.yml" }] } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'secret_detection template include is not an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => { "template" => "Security/Secret-Detection.gitlab-ci.yml" } } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + end + end + + context 'with no .gitlab-ci.yml' do + let(:gitlab_ci_content) { nil } + + context 'autodevops disabled' do + let(:auto_devops_enabled) { false } + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + include: + - template: Security/Secret-Detection.gitlab-ci.yml + CI_YML + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('create') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'with autodevops enabled' do + let(:auto_devops_enabled) { true } + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables + include: + - template: Auto-DevOps.gitlab-ci.yml + CI_YML + end + + before do + allow_next_instance_of(described_class) do |secret_detection_build_actions| + allow(secret_detection_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages) + end + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('create') + expect(result[:content]).to eq(expected_yml) + end + 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 +end diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..23a154b71a02924c183548784c1de64c1bfa9d68 --- /dev/null +++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'ConfigureSecretDetection' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :test_repo) } + + let(:variables) { { project_path: project.full_path } } + let(:mutation) { graphql_mutation(:configure_secret_detection, variables) } + let(:mutation_response) { graphql_mutation_response(:configureSecretDetection) } + + context 'when authorized' do + let_it_be(:user) { project.owner } + + it 'creates a branch with secret detection configured' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(mutation_response['branch']).not_to be_empty + expect(mutation_response['successPath']).not_to be_empty + end + end +end diff --git a/spec/services/security/ci_configuration/sast_create_service_spec.rb b/spec/services/security/ci_configuration/sast_create_service_spec.rb index ff7ab614e086eb7f714f4f1b6fd6358f8c6c5615..44f8f07a5beb4663534bd2694b9641098fbfe65b 100644 --- a/spec/services/security/ci_configuration/sast_create_service_spec.rb +++ b/spec/services/security/ci_configuration/sast_create_service_spec.rb @@ -3,67 +3,24 @@ require 'spec_helper' RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do - describe '#execute' do - let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { create(:user) } - let(:params) { {} } + subject(:result) { described_class.new(project, user, params).execute } - subject(:result) { described_class.new(project, user, params).execute } + let(:branch_name) { 'set-sast-config-1' } - context 'user does not belong to project' do - it 'returns an error status' do - expect(result[:status]).to eq(:error) - expect(result[:success_path]).to be_nil - end - - it 'does not track a snowplow event' do - subject - - expect_no_snowplow_event - end - end - - context 'user belongs to project' do - before do - project.add_developer(user) - end - - it 'does track the snowplow event' do - subject - - expect_snowplow_event( - category: 'Security::CiConfiguration::SastCreateService', - action: 'create', - label: 'false' - ) - end - - it 'raises exception if the user does not have permission to create a new branch' do - allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.") - - expect { subject }.to raise_error(Gitlab::Git::PreReceiveError) - end - - context 'with no parameters' do - it 'returns the path to create a new merge request' do - expect(result[:status]).to eq(:success) - expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) - end - end - - context 'with parameters' do - let(:params) do - { 'stage' => 'security', - 'SEARCH_MAX_DEPTH' => 1, - 'SECURE_ANALYZERS_PREFIX' => 'new_registry', - 'SAST_EXCLUDED_PATHS' => 'spec,docs' } - end + let(:non_empty_params) do + { 'stage' => 'security', + 'SEARCH_MAX_DEPTH' => 1, + 'SECURE_ANALYZERS_PREFIX' => 'new_registry', + 'SAST_EXCLUDED_PATHS' => 'spec,docs' } + end - it 'returns the path to create a new merge request' do - expect(result[:status]).to eq(:success) - expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) - end - end - end + let(:snowplow_event) do + { + category: 'Security::CiConfiguration::SastCreateService', + action: 'create', + label: 'false' + } end + + include_examples 'services security ci configuration create service' end diff --git a/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c1df3ebdca5bfc86618e572b00f363b54f9be65f --- /dev/null +++ b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow do + subject(:result) { described_class.new(project, user).execute } + + let(:branch_name) { 'set-secret-detection-config-1' } + + let(:snowplow_event) do + { + category: 'Security::CiConfiguration::SecretDetectionCreateService', + action: 'create', + label: '' + } + end + + include_examples 'services security ci configuration create service', true +end diff --git a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..2bb3d807aa7bbe23b8574c735de0dd9a4309a752 --- /dev/null +++ b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples_for 'graphql mutations security ci configuration' do + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:user) { create(:user) } + + let(:branch) do + "set-secret-config" + end + + let(:success_path) do + "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?" + end + + let(:service_response) do + ServiceResponse.success(payload: { branch: branch, success_path: success_path }) + end + + let(:error) { "An error occured!" } + + let(:service_error_response) do + ServiceResponse.error(message: error) + end + + specify { expect(described_class).to require_graphql_authorizations(:push_code) } + + describe '#resolve' do + let(:result) { subject } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when user does not have enough permissions' do + before do + project.add_guest(user) + end + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when user is a maintainer of a different project' do + before do + create(:project_empty_repo).add_maintainer(user) + end + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when the user does not have permission to create a new branch' do + let(:error_message) { 'You are not allowed to create protected branches on this project.' } + + before do + project.add_developer(user) + + allow_next_instance_of(::Files::MultiService) do |multi_service| + allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}")) + end + end + + it 'returns an array of errors' do + expect(result).to match( + branch: be_nil, + success_path: be_nil, + errors: match_array([error_message]) + ) + end + end + + context 'when the user can create a merge request' do + before do + project.add_developer(user) + end + + context 'when service successfully generates a path to create a new merge request' do + before do + allow_next_instance_of(service) do |service| + allow(service).to receive(:execute).and_return(service_response) + end + end + + it 'returns a success path' do + expect(result).to match( + branch: branch, + success_path: success_path, + errors: [] + ) + end + end + + context 'when service can not generate any path to create a new merge request' do + before do + allow_next_instance_of(service) do |service| + allow(service).to receive(:execute).and_return(service_error_response) + end + end + + it 'returns an array of errors' do + expect(result).to match( + branch: be_nil, + success_path: be_nil, + errors: match_array([error]) + ) + end + end + end + end +end diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..538fd2bb513e196be5279e765777160daf8a3d88 --- /dev/null +++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples_for 'services security ci configuration create service' do |skip_w_params| + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + describe '#execute' do + let(:params) { {} } + + context 'user does not belong to project' do + it 'returns an error status' do + expect(result.status).to eq(:error) + expect(result.payload[:success_path]).to be_nil + end + + it 'does not track a snowplow event' do + subject + + expect_no_snowplow_event + end + end + + context 'user belongs to project' do + before do + project.add_developer(user) + end + + it 'does track the snowplow event' do + subject + + expect_snowplow_event(**snowplow_event) + end + + it 'raises exception if the user does not have permission to create a new branch' do + allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.") + + expect { subject }.to raise_error(Gitlab::Git::PreReceiveError) + end + + context 'when exception is raised' do + let_it_be(:project) { create(:project, :repository) } + + before do + allow(project.repository).to receive(:add_branch).and_raise(StandardError, "The unexpected happened!") + end + + context 'when branch was created' do + before do + allow(project.repository).to receive(:branch_exists?).and_return(true) + end + + it 'tries to rm branch' do + expect(project.repository).to receive(:rm_branch).with(user, branch_name) + expect { subject }.to raise_error(StandardError) + end + end + + context 'when branch was not created' do + before do + allow(project.repository).to receive(:branch_exists?).and_return(false) + end + + it 'does not try to rm branch' do + expect(project.repository).not_to receive(:rm_branch) + expect { subject }.to raise_error(StandardError) + end + end + end + + context 'with no parameters' do + it 'returns the path to create a new merge request' do + expect(result.status).to eq(:success) + expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) + end + end + + unless skip_w_params + context 'with parameters' do + let(:params) { non_empty_params } + + it 'returns the path to create a new merge request' do + expect(result.status).to eq(:success) + expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) + end + end + end + end + end +end