Commit 3e6bba47 authored by Daniel Paul Searles's avatar Daniel Paul Searles Committed by Alex Kalderimis

Add ConfigureSecretDetection graphql mutation

parent 3cb9c129
......@@ -7,6 +7,11 @@ module Mutations
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 @@ module Mutations
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 @@ module Mutations
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
......
# 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
......@@ -16,6 +16,7 @@ module Types
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
......
# 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
......@@ -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
......
......@@ -74,7 +74,7 @@ module Security
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"]
......
# 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
---
title: Add ConfigureSecretDetection graphql mutation
merge_request: 58230
author:
type: added
......@@ -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
......@@ -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`
......
......@@ -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];
......
mutation configureSecretDetection($fullPath: ID!) {
configureSecretDetection(fullPath: $fullPath) {
mutation configureSecretDetection($input: ConfigureSecretDetectionInput!) {
configureSecretDetection(input: $input) {
successPath
errors
}
......
......@@ -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: {
......
......@@ -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'),
......
# 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
......@@ -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 @@ module Security
@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 @@ module Security
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
......
# 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
......@@ -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 ""
......
......@@ -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
# 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
# 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
# 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
......@@ -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
# 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
# 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment