Commit f6fad46c authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch '235545-remove-scanner-profiles-feature-flag' into 'master'

Remove security_on_demand_scans_scanner_profiles feature flag

See merge request gitlab-org/gitlab!41892
parents 7f281773 7e4169dd
......@@ -674,11 +674,6 @@ To delete an existing site profile:
## Scanner profile
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222767) in GitLab 13.4.
> - [Deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - Enabled on GitLab.com.
> - Can be enabled or disabled per-project.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can [disable this feature](#enable-or-disable-dast-scanner-profiles).
A scanner profile defines the scanner settings used to run an on-demand scan:
......@@ -713,29 +708,6 @@ To delete a scanner profile:
1. Click **Manage** in the **DAST Profiles** row.
1. Click **{remove}** in the scanner profile's row.
### Enable or disable DAST scanner profiles
The scanner profile feature is ready for production use. It's deployed behind a feature flag that
is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can opt to disable it.
To disable it:
```ruby
# Instance-wide
Feature.disable(:security_on_demand_scans_scanner_profiles)
# or by project
Feature.disable(:security_on_demand_scans_scanner_profiles, Project.find(<project id>))
```
To enable it:
```ruby
# Instance-wide
Feature.enable(:security_on_demand_scans_scanner_profiles)
# or by project
Feature.enable(:security_on_demand_scans_scanner_profiles, Project.find(<project ID>))
```
## On-demand scans
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2.
......
......@@ -6,7 +6,6 @@ import {
GlCard,
GlForm,
GlFormGroup,
GlIcon,
GlLink,
GlDropdown,
GlDropdownItem,
......@@ -48,7 +47,6 @@ export default {
GlCard,
GlForm,
GlFormGroup,
GlIcon,
GlLink,
GlDropdown,
GlDropdownItem,
......@@ -75,9 +73,6 @@ export default {
Sentry.captureException(e);
this.showErrors(ERROR_FETCH_SCANNER_PROFILES);
},
skip() {
return !this.glFeatures.securityOnDemandScansScannerProfiles;
},
},
siteProfiles: {
query: dastSiteProfilesQuery,
......@@ -129,9 +124,7 @@ export default {
scannerProfiles: [],
siteProfiles: [],
form: {
...(this.glFeatures.securityOnDemandScansScannerProfiles
? { dastScannerProfileId: initField(null) }
: {}),
dastScannerProfileId: initField(null),
dastSiteProfileId: initField(null),
},
loading: false,
......@@ -295,7 +288,7 @@ export default {
{{ s__('OnDemandScans|Scanner settings') }}
</h3>
</div>
<div v-if="glFeatures.securityOnDemandScansScannerProfiles" class="col-5 gl-text-right">
<div class="col-5 gl-text-right">
<gl-button
:href="scannerProfiles.length ? scannerProfilesLibraryPath : null"
:disabled="!scannerProfiles.length"
......@@ -310,109 +303,80 @@ export default {
</div>
</template>
<template v-if="glFeatures.securityOnDemandScansScannerProfiles">
<gl-form-group v-if="scannerProfiles.length">
<template #label>
{{ s__('OnDemandScans|Use existing scanner profile') }}
</template>
<gl-dropdown
v-model="form.dastScannerProfileId.value"
:text="scannerProfileText"
class="mw-460"
data-testid="scanner-profiles-dropdown"
<gl-form-group v-if="scannerProfiles.length">
<template #label>
{{ s__('OnDemandScans|Use existing scanner profile') }}
</template>
<gl-dropdown
v-model="form.dastScannerProfileId.value"
:text="scannerProfileText"
class="mw-460"
data-testid="scanner-profiles-dropdown"
>
<gl-dropdown-item
v-for="scannerProfile in scannerProfiles"
:key="scannerProfile.id"
:is-checked="form.dastScannerProfileId.value === scannerProfile.id"
is-check-item
@click="setScannerProfile(scannerProfile)"
>
<gl-dropdown-item
v-for="scannerProfile in scannerProfiles"
:key="scannerProfile.id"
:is-checked="form.dastScannerProfileId.value === scannerProfile.id"
is-check-item
@click="setScannerProfile(scannerProfile)"
>
{{ scannerProfile.profileName }}
</gl-dropdown-item>
</gl-dropdown>
<template v-if="selectedScannerProfile">
<hr />
<div data-testid="scanner-profile-summary">
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Scan mode') }}:</div>
<div class="col-md-9">
<strong>{{ s__('DastProfiles|Passive') }}</strong>
</div>
{{ scannerProfile.profileName }}
</gl-dropdown-item>
</gl-dropdown>
<template v-if="selectedScannerProfile">
<hr />
<div data-testid="scanner-profile-summary">
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Scan mode') }}:</div>
<div class="col-md-9">
<strong>{{ s__('DastProfiles|Passive') }}</strong>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Spider timeout') }}:</div>
<div class="col-md-9">
<strong>
{{ n__('%d minute', '%d minutes', selectedScannerProfile.spiderTimeout) }}
</strong>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Spider timeout') }}:</div>
<div class="col-md-9">
<strong>
{{ n__('%d minute', '%d minutes', selectedScannerProfile.spiderTimeout) }}
</strong>
</div>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Target timeout') }}:</div>
<div class="col-md-9">
<strong>
{{ n__('%d second', '%d seconds', selectedScannerProfile.targetTimeout) }}
</strong>
</div>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-3">{{ s__('DastProfiles|Target timeout') }}:</div>
<div class="col-md-9">
<strong>
{{ n__('%d second', '%d seconds', selectedScannerProfile.targetTimeout) }}
</strong>
</div>
</div>
</div>
</div>
</template>
</gl-form-group>
<template v-else>
<p class="gl-text-gray-700">
{{
s__(
'OnDemandScans|No profile yet. In order to create a new scan, you need to have at least one completed scanner profile.',
)
}}
</p>
<gl-button
:href="newScannerProfilePath"
variant="success"
category="secondary"
data-testid="create-scanner-profile-link"
>
{{ s__('OnDemandScans|Create a new scanner profile') }}
</gl-button>
</div>
</template>
</template>
</gl-form-group>
<template v-else>
<gl-form-group class="gl-mt-4">
<template #label>
{{ s__('OnDemandScans|Scan mode') }}
<gl-icon
v-gl-tooltip.hover
name="information-o"
class="gl-vertical-align-text-bottom gl-text-gray-600"
:title="s__('OnDemandScans|Only a passive scan can be performed on demand.')"
/>
</template>
{{ s__('OnDemandScans|Passive') }}
</gl-form-group>
<gl-form-group class="gl-mt-7 gl-mb-2">
<template #label>
{{ s__('OnDemandScans|Attached branch') }}
<gl-icon
v-gl-tooltip.hover
name="information-o"
class="gl-vertical-align-text-bottom gl-text-gray-600"
:title="s__('OnDemandScans|Attached branch is where the scan job runs.')"
/>
</template>
{{ defaultBranch }}
</gl-form-group>
<p class="gl-text-gray-700">
{{
s__(
'OnDemandScans|No profile yet. In order to create a new scan, you need to have at least one completed scanner profile.',
)
}}
</p>
<gl-button
:href="newScannerProfilePath"
variant="success"
category="secondary"
data-testid="create-scanner-profile-link"
>
{{ s__('OnDemandScans|Create a new scanner profile') }}
</gl-button>
</template>
</gl-card>
......
......@@ -4,7 +4,6 @@ import { GlDropdown, GlDropdownItem, GlTab, GlTabs } from '@gitlab/ui';
import { camelCase, kebabCase } from 'lodash';
import { s__ } from '~/locale';
import { getLocationHash } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProfilesList from './dast_profiles_list.vue';
import * as cacheUtils from '../graphql/cache_utils';
import { getProfileSettings } from '../settings/profiles';
......@@ -17,7 +16,6 @@ export default {
GlTabs,
ProfilesList,
},
mixins: [glFeatureFlagMixin()],
props: {
createNewProfilePaths: {
type: Object,
......@@ -37,14 +35,11 @@ export default {
},
computed: {
profileSettings() {
const { glFeatures, createNewProfilePaths } = this;
const { createNewProfilePaths } = this;
return getProfileSettings(
{
createNewProfilePaths,
},
glFeatures,
);
return getProfileSettings({
createNewProfilePaths,
});
},
tabIndex: {
get() {
......
......@@ -5,76 +5,61 @@ import dastScannerProfilesDelete from 'ee/security_configuration/dast_profiles/g
import { dastProfilesDeleteResponse } from 'ee/security_configuration/dast_profiles/graphql/cache_utils';
import { s__ } from '~/locale';
const hasNoFeatureFlagOrIsEnabled = glFeatures => ([, { featureFlag }]) => {
if (!featureFlag) {
return true;
}
return Boolean(glFeatures[featureFlag]);
};
export const getProfileSettings = ({ createNewProfilePaths }, glFeatures) => {
const settings = {
siteProfiles: {
profileType: 'siteProfiles',
createNewProfilePath: createNewProfilePaths.siteProfile,
graphQL: {
query: dastSiteProfilesQuery,
deletion: {
mutation: dastSiteProfilesDelete,
optimisticResponse: dastProfilesDeleteResponse({
mutationName: 'siteProfilesDelete',
payloadTypeName: 'DastSiteProfileDeletePayload',
}),
},
export const getProfileSettings = ({ createNewProfilePaths }) => ({
siteProfiles: {
profileType: 'siteProfiles',
createNewProfilePath: createNewProfilePaths.siteProfile,
graphQL: {
query: dastSiteProfilesQuery,
deletion: {
mutation: dastSiteProfilesDelete,
optimisticResponse: dastProfilesDeleteResponse({
mutationName: 'siteProfilesDelete',
payloadTypeName: 'DastSiteProfileDeletePayload',
}),
},
tableFields: ['profileName', 'targetUrl'],
i18n: {
createNewLinkText: s__('DastProfiles|Site Profile'),
tabName: s__('DastProfiles|Site Profiles'),
errorMessages: {
fetchNetworkError: s__(
'DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.',
),
deletionNetworkError: s__(
'DastProfiles|Could not delete site profile. Please refresh the page, or try again later.',
),
deletionBackendError: s__('DastProfiles|Could not delete site profiles:'),
},
},
tableFields: ['profileName', 'targetUrl'],
i18n: {
createNewLinkText: s__('DastProfiles|Site Profile'),
tabName: s__('DastProfiles|Site Profiles'),
errorMessages: {
fetchNetworkError: s__(
'DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.',
),
deletionNetworkError: s__(
'DastProfiles|Could not delete site profile. Please refresh the page, or try again later.',
),
deletionBackendError: s__('DastProfiles|Could not delete site profiles:'),
},
},
scannerProfiles: {
profileType: 'scannerProfiles',
createNewProfilePath: createNewProfilePaths.scannerProfile,
graphQL: {
query: dastScannerProfilesQuery,
deletion: {
mutation: dastScannerProfilesDelete,
optimisticResponse: dastProfilesDeleteResponse({
mutationName: 'scannerProfilesDelete',
payloadTypeName: 'DastScannerProfileDeletePayload',
}),
},
},
scannerProfiles: {
profileType: 'scannerProfiles',
createNewProfilePath: createNewProfilePaths.scannerProfile,
graphQL: {
query: dastScannerProfilesQuery,
deletion: {
mutation: dastScannerProfilesDelete,
optimisticResponse: dastProfilesDeleteResponse({
mutationName: 'scannerProfilesDelete',
payloadTypeName: 'DastScannerProfileDeletePayload',
}),
},
featureFlag: 'securityOnDemandScansScannerProfiles',
tableFields: ['profileName'],
i18n: {
createNewLinkText: s__('DastProfiles|Scanner Profile'),
tabName: s__('DastProfiles|Scanner Profiles'),
errorMessages: {
fetchNetworkError: s__(
'DastProfiles|Could not fetch scanner profiles. Please refresh the page, or try again later.',
),
deletionNetworkError: s__(
'DastProfiles|Could not delete scanner profile. Please refresh the page, or try again later.',
),
deletionBackendError: s__('DastProfiles|Could not delete scanner profiles:'),
},
},
tableFields: ['profileName'],
i18n: {
createNewLinkText: s__('DastProfiles|Scanner Profile'),
tabName: s__('DastProfiles|Scanner Profiles'),
errorMessages: {
fetchNetworkError: s__(
'DastProfiles|Could not fetch scanner profiles. Please refresh the page, or try again later.',
),
deletionNetworkError: s__(
'DastProfiles|Could not delete scanner profile. Please refresh the page, or try again later.',
),
deletionBackendError: s__('DastProfiles|Could not delete scanner profiles:'),
},
},
};
return Object.fromEntries(
Object.entries(settings).filter(hasNoFeatureFlagOrIsEnabled(glFeatures)),
);
};
},
});
......@@ -2,10 +2,7 @@
module Projects
class OnDemandScansController < Projects::ApplicationController
before_action do
authorize_read_on_demand_scans!
push_frontend_feature_flag(:security_on_demand_scans_scanner_profiles, project, default_enabled: true)
end
before_action :authorize_read_on_demand_scans!
def index
end
......
......@@ -4,9 +4,6 @@ module Projects
module Security
class DastProfilesController < Projects::ApplicationController
before_action :authorize_read_on_demand_scans!
before_action do
push_frontend_feature_flag(:security_on_demand_scans_scanner_profiles, project, default_enabled: true)
end
def show
end
......
---
name: security_on_demand_scans_scanner_profiles
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39250
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235545
group: group::dynamic analysis
type: development
default_enabled: true
......@@ -67,7 +67,6 @@ describe('OnDemandScansApp', () => {
},
},
provide: {
glFeatures: { securityOnDemandScansScannerProfiles: true },
scannerProfilesLibraryPath,
siteProfilesLibraryPath,
newScannerProfilePath,
......@@ -203,41 +202,6 @@ describe('OnDemandScansApp', () => {
});
});
describe('scanner profiles with feature flag disabled', () => {
beforeEach(() => {
createComponent({
provide: {
glFeatures: { securityOnDemandScansScannerProfiles: false },
},
});
});
it('shows static scanner settings and no scanner profiles component', () => {
expect(findScannerProfilesDropdown().exists()).toBe(false);
expect(findManageScannerProfilesButton().exists()).toBe(false);
expect(findCreateNewScannerProfileLink().exists()).toBe(false);
expect(wrapper.text()).toContain('Passive');
expect(wrapper.text()).toContain('master');
});
it('when submitting the form, GraphQL query does not include scanner data', async () => {
wrapper.vm.siteProfiles = siteProfiles;
await wrapper.vm.$nextTick();
jest
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue({ data: { dastOnDemandScanCreate: { pipelineUrl, errors: [] } } });
findSiteProfilesDropdown().vm.$emit('input', siteProfiles[0]);
submitForm();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: dastOnDemandScanCreate,
variables: {
dastSiteProfileId: siteProfiles[0],
fullPath: projectPath,
},
});
});
});
describe.each`
profileType | manageProfilesButtonFinder | manageProfilesPath | createNewProfileButtonFinder | newProfilePath | dropdownFinder
${'scanner'} | ${findManageScannerProfilesButton} | ${scannerProfilesLibraryPath} | ${findCreateNewScannerProfileLink} | ${newScannerProfilePath} | ${findScannerProfilesDropdown}
......
......@@ -36,12 +36,6 @@ describe('EE - DastProfiles', () => {
},
};
const defaultProvide = {
glFeatures: {
securityOnDemandScansScannerProfiles: true,
},
};
wrapper = mountFn(
DastProfiles,
merge(
......@@ -49,7 +43,6 @@ describe('EE - DastProfiles', () => {
{
propsData: defaultProps,
mocks: defaultMocks,
provide: defaultProvide,
},
options,
),
......@@ -59,24 +52,6 @@ describe('EE - DastProfiles', () => {
const createComponent = createComponentFactory();
const createFullComponent = createComponentFactory(mount);
const withFeatureFlag = (featureFlagName, { enabled, disabled }) => {
it.each([true, false])(`with ${featureFlagName} enabled: "%s"`, featureFlagStatus => {
createComponent({
provide: {
glFeatures: {
[featureFlagName]: featureFlagStatus,
},
},
});
if (featureFlagStatus) {
enabled();
} else {
disabled();
}
});
};
const withinComponent = () => within(wrapper.element);
const getProfilesComponent = profileType => wrapper.find(`[data-testid="${profileType}List"]`);
const getDropdownComponent = () => wrapper.find(GlDropdown);
......@@ -116,17 +91,10 @@ describe('EE - DastProfiles', () => {
);
});
describe(`shows a "Scanner Profile" dropdown item that links to ${TEST_NEW_DAST_SCANNER_PROFILE_PATH}`, () => {
withFeatureFlag('securityOnDemandScansScannerProfiles', {
enabled: () => {
expect(getSiteProfilesDropdownItem('Scanner Profile').getAttribute('href')).toBe(
TEST_NEW_DAST_SCANNER_PROFILE_PATH,
);
},
disabled: () => {
expect(getSiteProfilesDropdownItem('Scanner Profile')).toBe(null);
},
});
it(`shows a "Scanner Profile" dropdown item that links to ${TEST_NEW_DAST_SCANNER_PROFILE_PATH}`, () => {
expect(getSiteProfilesDropdownItem('Scanner Profile').getAttribute('href')).toBe(
TEST_NEW_DAST_SCANNER_PROFILE_PATH,
);
});
});
......
......@@ -17679,12 +17679,6 @@ msgstr ""
msgid "On track"
msgstr ""
msgid "OnDemandScans|Attached branch"
msgstr ""
msgid "OnDemandScans|Attached branch is where the scan job runs."
msgstr ""
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr ""
......@@ -17721,18 +17715,9 @@ msgstr ""
msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
msgstr ""
msgid "OnDemandScans|Only a passive scan can be performed on demand."
msgstr ""
msgid "OnDemandScans|Passive"
msgstr ""
msgid "OnDemandScans|Run scan"
msgstr ""
msgid "OnDemandScans|Scan mode"
msgstr ""
msgid "OnDemandScans|Scanner profile"
msgstr ""
......
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