Commit f4e16423 authored by David Pisek's avatar David Pisek Committed by Nathan Friend

DAST site profiles: Add header validation

This commit adds the http header validation method to the form
that let's a user create or edit a DAST on-demand site profile.
parent d97b63cd
---
name: security_on_demand_scans_http_header_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42812
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276403
milestone: '13.6'
type: development
group: group::dynamic analysis
default_enabled: false
...@@ -11,11 +11,16 @@ import { ...@@ -11,11 +11,16 @@ import {
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { omit } from 'lodash';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import download from '~/lib/utils/downloader'; import download from '~/lib/utils/downloader';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility'; import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { import {
DAST_SITE_VALIDATION_HTTP_HEADER_KEY,
DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
DAST_SITE_VALIDATION_METHOD_TEXT_FILE, DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS, DAST_SITE_VALIDATION_METHODS,
DAST_SITE_VALIDATION_STATUS, DAST_SITE_VALIDATION_STATUS,
...@@ -27,6 +32,7 @@ import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graph ...@@ -27,6 +32,7 @@ import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graph
export default { export default {
name: 'DastSiteValidation', name: 'DastSiteValidation',
components: { components: {
ClipboardButton,
GlAlert, GlAlert,
GlButton, GlButton,
GlCard, GlCard,
...@@ -38,6 +44,7 @@ export default { ...@@ -38,6 +44,7 @@ export default {
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlLoadingIcon,
}, },
mixins: [glFeatureFlagsMixin()],
apollo: { apollo: {
dastSiteValidation: { dastSiteValidation: {
query: dastSiteValidationQuery, query: dastSiteValidationQuery,
...@@ -103,6 +110,16 @@ export default { ...@@ -103,6 +110,16 @@ export default {
}; };
}, },
computed: { computed: {
validationMethodOptions() {
const isHttpHeaderValidationEnabled = this.glFeatures
.securityOnDemandScansHttpHeaderValidation;
const enabledValidationMethods = omit(DAST_SITE_VALIDATION_METHODS, [
!isHttpHeaderValidationEnabled ? DAST_SITE_VALIDATION_METHOD_HTTP_HEADER : '',
]);
return Object.values(enabledValidationMethods);
},
urlObject() { urlObject() {
try { try {
return new URL(this.targetUrl); return new URL(this.targetUrl);
...@@ -119,12 +136,18 @@ export default { ...@@ -119,12 +136,18 @@ export default {
isTextFileValidation() { isTextFileValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE; return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE;
}, },
isHttpHeaderValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_HTTP_HEADER;
},
textFileName() { textFileName() {
return `GitLab-DAST-Site-Validation-${this.token}.txt`; return `GitLab-DAST-Site-Validation-${this.token}.txt`;
}, },
locationStepLabel() { locationStepLabel() {
return DAST_SITE_VALIDATION_METHODS[this.validationMethod].i18n.locationStepLabel; return DAST_SITE_VALIDATION_METHODS[this.validationMethod].i18n.locationStepLabel;
}, },
httpHeader() {
return `${DAST_SITE_VALIDATION_HTTP_HEADER_KEY}: uuid-code-${this.token}`;
},
}, },
watch: { watch: {
targetUrl() { targetUrl() {
...@@ -132,13 +155,22 @@ export default { ...@@ -132,13 +155,22 @@ export default {
}, },
}, },
created() { created() {
this.unsubscribe = this.$watch(() => this.token, this.updateValidationPath, { this.unsubscribe = this.$watch(
immediate: true, () => [this.token, this.validationMethod],
}); this.updateValidationPath,
{
immediate: true,
},
);
}, },
methods: { methods: {
updateValidationPath() { updateValidationPath() {
this.validationPath = joinPaths(stripPathTail(this.path), this.textFileName); this.validationPath = this.isTextFileValidation
? this.getTextFileValidationPath()
: this.path;
},
getTextFileValidationPath() {
return joinPaths(stripPathTail(this.path), this.textFileName);
}, },
onValidationPathInput() { onValidationPathInput() {
this.unsubscribe(); this.unsubscribe();
...@@ -189,7 +221,6 @@ export default { ...@@ -189,7 +221,6 @@ export default {
this.hasValidationError = true; this.hasValidationError = true;
}, },
}, },
validationMethodOptions: Object.values(DAST_SITE_VALIDATION_METHODS),
}; };
</script> </script>
...@@ -199,7 +230,7 @@ export default { ...@@ -199,7 +230,7 @@ export default {
{{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }} {{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }}
</gl-alert> </gl-alert>
<gl-form-group :label="s__('DastProfiles|Step 1 - Choose site validation method')"> <gl-form-group :label="s__('DastProfiles|Step 1 - Choose site validation method')">
<gl-form-radio-group v-model="validationMethod" :options="$options.validationMethodOptions" /> <gl-form-radio-group v-model="validationMethod" :options="validationMethodOptions" />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="isTextFileValidation" v-if="isTextFileValidation"
...@@ -217,6 +248,16 @@ export default { ...@@ -217,6 +248,16 @@ export default {
{{ textFileName }} {{ textFileName }}
</gl-button> </gl-button>
</gl-form-group> </gl-form-group>
<gl-form-group
v-else-if="isHttpHeaderValidation"
:label="s__('DastProfiles|Step 2 - Add following HTTP header to your site')"
>
<code class="gl-p-3 gl-bg-black gl-text-white">{{ httpHeader }}</code>
<clipboard-button
:text="httpHeader"
:title="s__('DastProfiles|Copy HTTP header to clipboard')"
/>
</gl-form-group>
<gl-form-group :label="locationStepLabel" class="mw-460"> <gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group> <gl-form-input-group>
<template #prepend> <template #prepend>
...@@ -255,7 +296,7 @@ export default { ...@@ -255,7 +296,7 @@ export default {
<gl-icon name="status_failed" /> <gl-icon name="status_failed" />
{{ {{
s__( s__(
'DastProfiles|Validation failed, please make sure that you follow the steps above with the choosen method.', 'DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method.',
) )
}} }}
</template> </template>
......
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const DAST_SITE_VALIDATION_METHOD_TEXT_FILE = 'TEXT_FILE'; export const DAST_SITE_VALIDATION_METHOD_TEXT_FILE = 'TEXT_FILE';
export const DAST_SITE_VALIDATION_METHOD_HTTP_HEADER = 'HTTP_HEADER';
export const DAST_SITE_VALIDATION_METHODS = { export const DAST_SITE_VALIDATION_METHODS = {
[DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: { [DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: {
value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE, value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
...@@ -9,6 +11,13 @@ export const DAST_SITE_VALIDATION_METHODS = { ...@@ -9,6 +11,13 @@ export const DAST_SITE_VALIDATION_METHODS = {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm text file location and validate'), locationStepLabel: s__('DastProfiles|Step 3 - Confirm text file location and validate'),
}, },
}, },
[DAST_SITE_VALIDATION_METHOD_HTTP_HEADER]: {
value: DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
text: s__('DastProfiles|Header validation'),
i18n: {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm header location and validate'),
},
},
}; };
export const DAST_SITE_VALIDATION_STATUS = { export const DAST_SITE_VALIDATION_STATUS = {
...@@ -19,3 +28,4 @@ export const DAST_SITE_VALIDATION_STATUS = { ...@@ -19,3 +28,4 @@ export const DAST_SITE_VALIDATION_STATUS = {
}; };
export const DAST_SITE_VALIDATION_POLL_INTERVAL = 1000; export const DAST_SITE_VALIDATION_POLL_INTERVAL = 1000;
export const DAST_SITE_VALIDATION_HTTP_HEADER_KEY = 'Gitlab-On-Demand-DAST';
...@@ -6,6 +6,7 @@ module Projects ...@@ -6,6 +6,7 @@ module Projects
before_action do before_action do
authorize_read_on_demand_scans! authorize_read_on_demand_scans!
push_frontend_feature_flag(:security_on_demand_scans_site_validation, @project) push_frontend_feature_flag(:security_on_demand_scans_site_validation, @project)
push_frontend_feature_flag(:security_on_demand_scans_http_header_validation, @project)
end end
feature_category :dynamic_application_security_testing feature_category :dynamic_application_security_testing
......
...@@ -14,7 +14,7 @@ export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING) ...@@ -14,7 +14,7 @@ export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING)
export const dastSiteValidationCreate = (errors = []) => ({ export const dastSiteValidationCreate = (errors = []) => ({
data: { data: {
dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PASSED, id: '1', errors }, dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PENDING, id: '1', errors },
}, },
}); });
......
...@@ -8319,6 +8319,9 @@ msgstr "" ...@@ -8319,6 +8319,9 @@ msgstr ""
msgid "DastProfiles|Authentication URL" msgid "DastProfiles|Authentication URL"
msgstr "" msgstr ""
msgid "DastProfiles|Copy HTTP header to clipboard"
msgstr ""
msgid "DastProfiles|Could not create site validation token. Please refresh the page, or try again later." msgid "DastProfiles|Could not create site validation token. Please refresh the page, or try again later."
msgstr "" msgstr ""
...@@ -8385,6 +8388,9 @@ msgstr "" ...@@ -8385,6 +8388,9 @@ msgstr ""
msgid "DastProfiles|Error Details" msgid "DastProfiles|Error Details"
msgstr "" msgstr ""
msgid "DastProfiles|Header validation"
msgstr ""
msgid "DastProfiles|Hide debug messages" msgid "DastProfiles|Hide debug messages"
msgstr "" msgstr ""
...@@ -8469,9 +8475,15 @@ msgstr "" ...@@ -8469,9 +8475,15 @@ msgstr ""
msgid "DastProfiles|Step 1 - Choose site validation method" msgid "DastProfiles|Step 1 - Choose site validation method"
msgstr "" msgstr ""
msgid "DastProfiles|Step 2 - Add following HTTP header to your site"
msgstr ""
msgid "DastProfiles|Step 2 - Add following text to the target site" msgid "DastProfiles|Step 2 - Add following text to the target site"
msgstr "" msgstr ""
msgid "DastProfiles|Step 3 - Confirm header location and validate"
msgstr ""
msgid "DastProfiles|Step 3 - Confirm text file location and validate" msgid "DastProfiles|Step 3 - Confirm text file location and validate"
msgstr "" msgstr ""
...@@ -8508,7 +8520,7 @@ msgstr "" ...@@ -8508,7 +8520,7 @@ msgstr ""
msgid "DastProfiles|Validating..." msgid "DastProfiles|Validating..."
msgstr "" msgstr ""
msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the choosen method." msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method."
msgstr "" msgstr ""
msgid "DastProfiles|Validation failed. Please try again." msgid "DastProfiles|Validation failed. Please try again."
......
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