Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-24 03:07:40 +00:00
parent 8212423bb8
commit a908d3a2c8
39 changed files with 634 additions and 369 deletions

View File

@ -187,14 +187,14 @@ module ApplicationHelper
css_classes << html_class unless html_class.blank?
content_tag :time, l(time, format: "%b %d, %Y"),
class: css_classes.join(' '),
title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
datetime: time.to_time.getutc.iso8601,
data: {
toggle: 'tooltip',
placement: placement,
container: 'body'
}
class: css_classes.join(' '),
title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
datetime: time.to_time.getutc.iso8601,
data: {
toggle: 'tooltip',
placement: placement,
container: 'body'
}
end
def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', exclude_author: false)
@ -279,7 +279,15 @@ module ApplicationHelper
end
def stylesheet_link_tag_defer(path)
stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
if startup_css_enabled?
stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
else
stylesheet_link_tag(path, crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
end
end
def startup_css_enabled?
!params.has_key?(:no_startup_css)
end
def outdated_browser?

View File

@ -18,7 +18,13 @@
= favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
= render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) }
- if startup_css_enabled?
= render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) }
- else
- diffs_colors = user_diffs_colors
= stylesheet_link_tag "themes/#{user_application_theme_css_filename}"
= render 'layouts/diffs_colors_css', diffs_colors if diffs_colors.present? || request.path == profile_preferences_path
- if user_application_theme == 'gl-dark'
%meta{ name: 'color-scheme', content: 'dark light' }
= stylesheet_link_tag_defer "application_dark"
@ -33,7 +39,8 @@
= stylesheet_link_tag_defer "highlight/themes/#{user_color_scheme}"
= render 'layouts/startup_css_activation'
- if startup_css_enabled?
= render 'layouts/startup_css_activation'
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?

View File

@ -97,6 +97,55 @@ Returns:
- `200 OK` with response containing the licenses in JSON format. This is an empty JSON array if there are no licenses.
- `403 Forbidden` if the current user in not permitted to read the licenses.
## Retrieve information about a single license
```plaintext
GET /license/:id
```
Supported attributes:
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------------|
| `id` | integer | yes | ID of the GitLab license. |
Returns the following status codes:
- `200 OK`: Response contains the licenses in JSON format.
- `404 Not Found`: The requested license doesn't exist.
- `403 Forbidden`: The current user is not permitted to read the licenses.
Example request:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/license/:id"
```
Example response:
```json
{
"id": 1,
"plan": "premium",
"created_at": "2018-02-27T23:21:58.674Z",
"starts_at": "2018-01-27",
"expires_at": "2022-01-27",
"historical_max": 300,
"maximum_user_count": 300,
"expired": false,
"overage": 200,
"user_limit": 100,
"active_users": 50,
"licensee": {
"Name": "John Doe1"
},
"add_ons": {
"GitLab_FileLocks": 1,
"GitLab_Auditor_User": 1
}
}
```
## Add a new license
```plaintext

View File

@ -0,0 +1,359 @@
---
stage: Secure
group: Dynamic Analysis
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
type: reference, howto
---
# Authentication (DAST) **(ULTIMATE)**
WARNING:
**Never** run an authenticated scan against a production server.
Authenticated scans may perform *any* function that the authenticated user can,
including modifying or deleting data, submitting forms, and following links.
Only run an authenticated scan against a test server.
Authentication logs a user in before a DAST scan so that the analyzer can test
as much of the application as possible when searching for vulnerabilities.
DAST uses a browser to authenticate the user so that the login form has the necessary JavaScript
and styling required to submit the form. DAST finds the username and password fields and fills them with their respective values.
The login form is submitted, and when the response returns, a series of checks verify if authentication was successful.
DAST saves the credentials for reuse when crawling the target application.
If DAST fails to authenticate, the scan halts and the CI job fails.
Authentication supports single-step login forms, multi-step login forms, single sign-on, and authenticating to URLs outside of the configured target URL.
## Getting started
NOTE:
We recommend periodically confirming that the analyzer's authentication is still working, as this tends to break over
time due to changes to the application.
To run a DAST authenticated scan:
- Read the [prerequisite](#prerequisites) conditions for authentication.
- [Update your target website](#update-the-target-website) to a landing page of an authenticated user.
- If your login form has the username, password and submit button on a single page, use the [CI/CD variables](#available-cicd-variables) to configure [single-step](#configuration-for-a-single-step-login-form) login form authentication.
- If your login form has the username and password fields on different pages, use the [CI/CD variables](#available-cicd-variables) to configure [multi-step](#configuration-for-a-multi-step-login-form) login form authentication.
- Make sure the user isn't [logged out](#excluding-logout-urls) during the scan.
### Prerequisites
- You are using either the [DAST proxy-based analyzer](proxy-based.md) or the [DAST browser-based analyzer](browser_based.md).
- You know the URL of the login form of your application. Alternatively, you know how to navigate to the login form from the authentication URL (see [clicking to navigate to the login form](#clicking-to-navigate-to-the-login-form)).
- You have the username and password of the user you would like to authenticate as during the scan.
- You know the [selectors](#finding-an-elements-selector) of the username and password HTML fields that DAST will use to input the respective values.
- You know the element's [selector](#finding-an-elements-selector) that will submit the login form when selected.
- You have thought about how you can [verify](#verifying-authentication-is-successful) whether or not authentication was successful.
- You have checked the [known limitations](#known-limitations) to ensure DAST can authenticate to your application.
### Available CI/CD variables
| CI/CD variable | Type | Description |
|:-----------------------------------------------|:--------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `DAST_AUTH_REPORT` | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
| `DAST_AUTH_URL` <sup>1</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Example: `https://login.example.com`. |
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` | boolean | Verifies successful authentication by checking for the absence of a login form once the login form has been submitted. |
| `DAST_AUTH_VERIFICATION_SELECTOR` | selector | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo`. |
| `DAST_AUTH_VERIFICATION_URL` <sup>1</sup> | URL | Verifies successful authentication by checking the URL in the browser once the login form has been submitted. Example: `"https://example.com/loggedin_page"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
| `DAST_BROWSER_PATH_TO_LOGIN_FORM` <sup>1</sup> | selector | Comma-separated list of selectors that are selected prior to attempting to enter `DAST_USERNAME` and `DAST_PASSWORD` into the login form. Example: `"css:.navigation-menu,css:.login-menu-item"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326633) in GitLab 14.1. |
| `DAST_EXCLUDE_URLS` <sup>1</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. |
| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the username form of a multi-page login process. For example, `css:button[type='user-submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_PASSWORD` <sup>1</sup> | string | The password to authenticate to in the website. Example: `P@55w0rd!` |
| `DAST_PASSWORD_FIELD` | string | The selector of password field at the sign-in HTML form. Example: `id:password` |
| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the login form or the password form of a multi-page login process. For example, `css:button[type='submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_USERNAME` <sup>1</sup> | string | The username to authenticate to in the website. Example: `admin` |
| `DAST_USERNAME_FIELD` <sup>1</sup> | string | The selector of username field at the sign-in HTML form. Example: `name:username` |
1. Available to an on-demand proxy-based DAST scan.
### Update the target website
The target website, defined using the CI/CD variable `DAST_WEBSITE`, is the URL DAST uses to begin crawling your application.
For best crawl results on an authenticated scan, the target website should be a URL accessible only after the user is authenticated.
Often, this is the URL of the page the user lands on after they're logged in.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com/dashboard/welcome"
DAST_AUTH_URL: "https://example.com/login"
```
### Configuration for a single-step login form
A single-step login form has all login form elements on a single page.
Configuration requires the CI/CD variables `DAST_AUTH_URL`, `DAST_USERNAME`, `DAST_USERNAME_FIELD`, `DAST_PASSWORD`, `DAST_PASSWORD_FIELD`, and `DAST_SUBMIT_FIELD` to be defined for the DAST job.
It is recommended to set up the URL and selectors of fields in the job definition YAML, for example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_URL: "https://example.com/login"
DAST_USERNAME_FIELD: "css:[name=username]"
DAST_PASSWORD_FIELD: "css:[name=password]"
DAST_SUBMIT_FIELD: "css:button[type=submit]"
```
Do **not** define `DAST_USERNAME` and `DAST_PASSWORD` in the YAML job definition file as this could present a security risk. Instead, create them as masked CI/CD variables using the GitLab UI.
See [Custom CI/CI variables](../../../ci/variables/index.md#custom-cicd-variables) for more information.
### Configuration for a multi-step login form
A multi-step login form has two pages. The first page has a form with the username and a next submit button.
If the username is valid, a second form on the subsequent page has the password and the form submit button.
Configuration requires the CI/CD variables `DAST_AUTH_URL`, `DAST_USERNAME`, `DAST_USERNAME_FIELD`, `DAST_FIRST_SUBMIT_FIELD`, `DAST_PASSWORD`, `DAST_PASSWORD_FIELD`, and `DAST_SUBMIT_FIELD` to be defined for the DAST job.
It is recommended to set up the URL and selectors of fields in the job definition YAML, for example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_URL: "https://example.com/login"
DAST_USERNAME_FIELD: "css:[name=username]"
DAST_FIRST_SUBMIT_FIELD: "css:button[name=next]"
DAST_PASSWORD_FIELD: "css:[name=password]"
DAST_SUBMIT_FIELD: "css:button[type=submit]"
```
Do **not** define `DAST_USERNAME` and `DAST_PASSWORD` in the YAML job definition file as this could present a security risk. Instead, create them as masked CI/CD variables using the GitLab UI.
See [Custom CI/CI variables](../../../ci/variables/index.md#custom-cicd-variables) for more information.
### Configuration for Single Sign-On (SSO)
If a user can log into an application, then in most cases, DAST will also be able to log in.
This is the case even when an application uses Single Sign-on. Applications using SSO solutions should configure DAST
authentication using the [single-step](#configuration-for-a-single-step-login-form) or [multi-step](#configuration-for-a-multi-step-login-form) login form configuration guides.
DAST supports authentication processes where a user is redirected to an external Identity Provider's site to log in.
Check the [known limitations](#known-limitations) of DAST authentication to determine if your SSO authentication process is supported.
### Clicking to navigate to the login form
Define `DAST_BROWSER_PATH_TO_LOGIN_FORM` to provide a path of elements to click on from the `DAST_AUTH_URL` so that DAST can access the
login form. This is useful for applications that show the login form in a pop-up (modal) window or when the login form does not
have a unique URL.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_URL: "https://example.com/login"
DAST_BROWSER_PATH_TO_LOGIN_FORM: "css:.navigation-menu,css:.login-menu-item"
```
### Excluding logout URLs
If DAST crawls the logout URL while running an authenticated scan, the user will be logged out, resulting in the remainder of the scan being unauthenticated.
It is therefore recommended to exclude logout URLs using the CI/CD variable `DAST_EXCLUDE_URLS`. DAST will not access any excluded URLs, ensuring the user remains logged in.
Provided URLs can be either absolute URLs, or regular expressions of URL paths relative to the base path of the `DAST_WEBSITE`. For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com/welcome/home"
DAST_EXCLUDE_URLS: "https://example.com/logout,/user/.*/logout"
```
### Finding an element's selector
Selectors are used by CI/CD variables to specify the location of an element displayed on a page in a browser.
Selectors have the format `type`:`search string`. DAST searches for the selector using the search string based on the type.
| Selector type | Example | Description |
| ------------- | ---------------------------------- | ----------- |
| `css` | `css:.password-field` | Searches for a HTML element having the supplied CSS selector. Selectors should be as specific as possible for performance reasons. |
| `id` | `id:element` | Searches for an HTML element with the provided element ID. |
| `name` | `name:element` | Searches for an HTML element with the provided element name. |
| `xpath` | `xpath://input[@id="my-button"]/a` | Searches for a HTML element with the provided XPath. Note that XPath searches are expected to be less performant than other searches. |
| None provided | `a.click-me` | Defaults to searching using a CSS selector. |
#### Find selectors with Google Chrome
Chrome DevTools element selector tool is an effective way to find a selector.
1. Open Chrome and navigate to the page where you would like to find a selector, for example, the login page for your site.
1. Open the `Elements` tab in Chrome DevTools with the keyboard shortcut `Command + Shift + c` in macOS or `Ctrl + Shift + c` in Windows.
1. Select the `Select an element in the page to select it` tool.
![search-elements](img/dast_auth_browser_scan_search_elements.png)
1. Select the field on your page that you would like to know the selector for.
1. Once the tool is active, highlight a field you wish to view the details of.
![highlight](img/dast_auth_browser_scan_highlight.png)
1. Once highlighted, you can see the element's details, including attributes that would make a good candidate for a selector.
In this example, the `id="user_login"` appears to be a good candidate. You can use this as a selector as the DAST username field by setting
`DAST_USERNAME_FIELD: "id:user_login"`.
#### Choose the right selector
Judicious choice of selector leads to a scan that is resilient to the application changing.
In order of preference, it is recommended to choose as selectors:
- `id` fields. These are generally unique on a page, and rarely change.
- `name` fields. These are generally unique on a page, and rarely change.
- `class` values specific to the field, such as the selector `"css:.username"` for the `username` class on the username field.
- Presence of field specific data attributes, such as the selector, `"css:[data-username]"` when the `data-username` field has any value on the username field.
- Multiple `class` hierarchy values, such as the selector `"css:.login-form .username"` when there are multiple elements with class `username` but only one nested inside the element with the class `login-form`.
When using selectors to locate specific fields we recommend you avoid searching on:
- Any `id`, `name`, `attribute`, `class` or `value` that is dynamically generated.
- Generic class names, such as `column-10` and `dark-grey`.
- XPath searches as they are less performant than other selector searches.
- Unscoped searches, such as those beginning with `css:*` and `xpath://*`.
## Verifying authentication is successful
Once DAST has submitted the login form, a verification process takes place
to determine if authentication succeeded. The scan will halt with an error if authentication is unsuccessful.
Following the submission of the login form, authentication is determined to be unsuccessful when:
- The login submit HTTP response has a `400` or `500` series status code.
- Any [verification check](#verification-checks) fails.
- An [authentication token](#authentication-tokens) with a sufficiently random value is not set during the authentication process.
### Verification checks
Verification checks run checks on the state of the browser once authentication is complete
to determine further if authentication succeeded.
DAST will test for the absence of a login form if no verification checks are configured.
#### Verify based on the URL
Define `DAST_AUTH_VERIFICATION_URL` as the URL displayed in the browser tab once the login form is successfully submitted.
DAST will compare the verification URL to the URL in the browser after authentication.
If they are not the same, authentication is unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_VERIFICATION_URL: "https://example.com/user/welcome"
```
#### Verify based on presence of an element
Define `DAST_AUTH_VERIFICATION_SELECTOR` as a [selector](#finding-an-elements-selector) that will find one or many elements on the page
displayed once the login form is successfully submitted. If no element is found, authentication is unsuccessful.
Searching for the selector on the page displayed when login fails should return no elements.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_VERIFICATION_SELECTOR: "css:.welcome-user"
```
#### Verify based on absence of a login form
Define `DAST_AUTH_VERIFICATION_LOGIN_FORM` as `"true"` to indicate that DAST should search for the login form on the
page displayed once the login form is successfully submitted. If a login form is still present after logging in, authentication is unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_VERIFICATION_LOGIN_FORM: "true"
```
### Authentication tokens
DAST records authentication tokens set during the authentication process.
Authentication tokens are loaded into new browsers when DAST opens them so the user can remain logged in throughout the scan.
To record tokens, DAST takes a snapshot of cookies, local storage, and session storage values set by the application before
the authentication process. DAST does the same after authentication and uses the difference to determine which were created
by the authentication process.
DAST considers cookies, local storage and session storage values set with sufficiently "random" values to be authentication tokens.
For example, `sessionID=HVxzpS8GzMlPAc2e39uyIVzwACIuGe0H` would be viewed as an authentication token, while `ab_testing_group=A1` would not.
## Known limitations
- DAST cannot bypass a CAPTCHA if the authentication flow includes one. Please turn these off in the testing environment for the application being scanned.
- DAST cannot handle multi-factor authentication like one-time passwords (OTP) by using SMS, biometrics, or authenticator apps. Please turn these off in the testing environment for the application being scanned.
- DAST cannot authenticate to applications that do not set an [authentication token](#authentication-tokens) during login.
- DAST cannot authenticate to applications that require more than two inputs to be filled out. Two inputs must be supplied, username and password.
## Troubleshooting
### Read the logs
The console output of the DAST CI/CD job shows information about the authentication process using the `AUTH` log module.
For example, the following log shows failed authentication for a multi-step login form.
Authentication failed because a home page should be displayed after login. Instead, the login form was still present.
```plaintext
2022-11-16T13:43:02.000 INF AUTH attempting to authenticate
2022-11-16T13:43:02.000 INF AUTH loading login page LoginURL=https://example.com/login
2022-11-16T13:43:10.000 INF AUTH multi-step authentication detected
2022-11-16T13:43:20.000 INF AUTH verifying if login attempt was successful true_when="no login form found (no element found when searching using selector css:[id=email] or css:[id=password] or css:[id=submit])"
2022-11-16T13:43:21.000 INF AUTH requirement is unsatisfied, login form was found want="no login form found (no element found when searching using selector css:[id=email] or css:[id=password] or css:[id=submit])"
2022-11-16T13:43:21.000 INF AUTH login attempt failed error="authentication failed: failed to authenticate user"
```
### Configure the authentication debug report
An authentication report can be saved as a CI/CD job artifact to assist with understanding the cause of an authentication failure.
The report contains steps during the login process, HTTP requests and responses, the Document Object Model (DOM) and screenshots.
![dast-auth-report](img/dast_auth_report.jpg)
An example configuration where the authentication debug report is exported may look like the following:
```yaml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_AUTH_REPORT: "true"
artifacts:
paths: [gl-dast-debug-auth-report.html]
when: always
```

View File

@ -52,9 +52,14 @@ dast:
DAST_BROWSER_SCAN: "true"
```
## Authentication
The browser-based analyzer can authenticate a user prior to a scan. See [Authentication](authentication.md) for configuration instructions.
### Available CI/CD variables
The browser-based crawler can be configured using CI/CD variables.
These CI/CD variables are specific to DAST. They can be used to customize the behavior of DAST to your requirements.
For authentication CI/CD variables, see [Authentication](authentication.md).
| CI/CD variable | Type | Example | Description |
|----------------------------------------------| ----------------| --------------------------------- | ------------|

View File

@ -248,6 +248,10 @@ When using `DAST_PATHS` and `DAST_PATHS_FILE`, note the following:
To perform a [full scan](#full-scan) on the listed paths, use the `DAST_FULL_SCAN_ENABLED` CI/CD variable.
## Authentication
The proxy-based analyzer uses the browser-based analyzer to authenticate a user prior to a scan. See [Authentication](authentication.md) for configuration instructions.
## Customize DAST settings
You can customize the behavior of DAST using both CI/CD variables and command-line options. Use of CI/CD
@ -339,61 +343,49 @@ To enable Mutual TLS:
#### Available CI/CD variables
These CI/CD variables are specific to DAST. They can be used to customize the behavior of DAST to your requirements.
For authentication CI/CD variables, see [Authentication](authentication.md).
WARNING:
All customization of GitLab security scanning tools should be tested in a merge request before
merging these changes to the default branch. Failure to do so can give unexpected results,
including a large number of false positives.
| CI/CD variable | Type | Description |
|:-------------------------------------------------|:--------------|:------------------------------|
| `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. |
| `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. |
| `DAST_API_HOST_OVERRIDE` <sup>1</sup> | string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080`. |
| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. |
| `DAST_AUTH_REPORT` <sup>2</sup> | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
| `DAST_AUTH_EXCLUDE_URLS` <sup>2</sup> | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. |
| `DAST_AUTH_URL` <sup>1,2</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Example: `https://login.example.com`. |
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` <sup>2</sup> | boolean | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
| `DAST_AUTH_VERIFICATION_SELECTOR` <sup>2</sup> | selector | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo`. |
| `DAST_AUTH_VERIFICATION_URL` <sup>1,2</sup> | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. Example: `"http://example.com/loggedin_page"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false`. |
| `DAST_BROWSER_PATH_TO_LOGIN_FORM` <sup>1,2</sup> | selector | Comma-separated list of selectors that are selected prior to attempting to enter `DAST_USERNAME` and `DAST_PASSWORD` into the login form. Example: `"css:.navigation-menu,css:.login-menu-item"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326633) in GitLab 14.1. |
| `DAST_DEBUG` <sup>1</sup> | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
| `DAST_EXCLUDE_URLS` <sup>1,2</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Example, `http://example.com/sign-out`. |
| `DAST_FIRST_SUBMIT_FIELD` <sup>2</sup> | string | The `id` or `name` of the element that when selected submits the username form of a multi-page login process. For example, `css:button[type='user-submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/293595)** in GitLab 14.0. Set to `true` to require domain validation when running DAST full scans. Default: `false` |
| `DAST_FULL_SCAN_ENABLED` <sup>1</sup> | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
| `DAST_HTML_REPORT` | string | The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
| `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. |
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
| `DAST_PASSWORD` <sup>1,2</sup> | string | The password to authenticate to in the website. Example: `P@55w0rd!` |
| `DAST_PASSWORD_FIELD` <sup>1,2</sup> | string | The selector of password field at the sign-in HTML form. Example: `id:password` |
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
| `DAST_PKCS12_CERTIFICATE_BASE64` | string | The PKCS12 certificate used for sites that require Mutual TLS. Must be encoded as base64 text. |
| `DAST_PKCS12_PASSWORD` | string | The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. |
| `DAST_REQUEST_HEADERS` <sup>1</sup> | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
| `DAST_SPIDER_MINS` <sup>1</sup> | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
| `DAST_SUBMIT_FIELD` <sup>2</sup> | string | The `id` or `name` of the element that when selected submits the login form or the password form of a multi-page login process. For example, `css:button[type='submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_TARGET_AVAILABILITY_TIMEOUT` <sup>1</sup> | number | Time limit in seconds to wait for target availability. |
| `DAST_USE_AJAX_SPIDER` <sup>1</sup> | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_USERNAME` <sup>1,2</sup> | string | The username to authenticate to in the website. Example: `admin` |
| `DAST_USERNAME_FIELD` <sup>1,2</sup> | string | The selector of username field at the sign-in HTML form. Example: `name:username` |
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. |
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. Example: `logger.httpsender.name=org.parosproxy.paros.network.HttpSender;logger.httpsender.level=debug;logger.sitemap.name=org.parosproxy.paros.model.SiteMap;logger.sitemap.level=debug;` |
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
| CI/CD variable | Type | Description |
|:------------------------------------------------|:--------------|:------------------------------|
| `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. |
| `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. |
| `DAST_API_HOST_OVERRIDE` <sup>1</sup> | string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080`. |
| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/383467)** in GitLab 15.7. Replaced by [DAST API scan](../dast_api/index.md#available-cicd-variables). The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. |
| `DAST_AUTH_EXCLUDE_URLS` | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. |
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false`. |
| `DAST_DEBUG` <sup>1</sup> | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
| `DAST_EXCLUDE_URLS` <sup>1</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Example, `http://example.com/sign-out`. |
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/293595)** in GitLab 14.0. Set to `true` to require domain validation when running DAST full scans. Default: `false` |
| `DAST_FULL_SCAN_ENABLED` <sup>1</sup> | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
| `DAST_HTML_REPORT` | string | The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
| `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. |
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
| `DAST_PKCS12_CERTIFICATE_BASE64` | string | The PKCS12 certificate used for sites that require Mutual TLS. Must be encoded as base64 text. |
| `DAST_PKCS12_PASSWORD` | string | The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. |
| `DAST_REQUEST_HEADERS` <sup>1</sup> | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
| `DAST_SPIDER_MINS` <sup>1</sup> | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
| `DAST_TARGET_AVAILABILITY_TIMEOUT` <sup>1</sup> | number | Time limit in seconds to wait for target availability. |
| `DAST_USE_AJAX_SPIDER` <sup>1</sup> | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. |
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. Example: `logger.httpsender.name=org.parosproxy.paros.network.HttpSender;logger.httpsender.level=debug;logger.sitemap.name=org.parosproxy.paros.model.SiteMap;logger.sitemap.level=debug;` |
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
1. Available to an on-demand DAST scan.
1. Used for authentication.
### Customize DAST using command-line options
@ -440,242 +432,6 @@ variables:
DAST_ZAP_CLI_OPTIONS: "-config replacer.full_list(0).description=auth -config replacer.full_list(0).enabled=true -config replacer.full_list(0).matchtype=REQ_HEADER -config replacer.full_list(0).matchstr=Authorization -config replacer.full_list(0).regex=false -config replacer.full_list(0).replacement=TOKEN"
```
## Authentication
NOTE:
We highly recommend you configure the scanner to authenticate to the application. If you don't, it cannot check most of the application for security risks, as most
of your application is likely not accessible without authentication. We also recommend
you periodically confirm the scanner's authentication is still working, as this tends to break over
time due to authentication changes to the application.
Create masked CI/CD variables to pass the credentials that DAST uses.
To create masked variables for the username and password, see [Create a custom variable in the UI](../../../ci/variables/index.md#custom-cicd-variables).
The key of the username variable must be `DAST_USERNAME`,
and the key of the password variable must be `DAST_PASSWORD`.
After DAST has authenticated with the application, all cookies are collected from the web browser.
For each cookie a matching session token is created for use by ZAP. This ensures ZAP is recognized
by the application as correctly authenticated.
Authentication supports single form logins, multi-step login forms, and authenticating to URLs outside of the configured target URL.
WARNING:
**Never** run an authenticated scan against a production server. When an authenticated
scan is run, it may perform *any* function that the authenticated user can. This
includes actions like modifying and deleting data, submitting forms, and following links.
Only run an authenticated scan against a test server.
### SSO
DAST can authenticate to websites making use of SSO, with the following restrictions:
- DAST cannot bypass a CAPTCHA if the authentication flow includes one.
- DAST cannot handle multi-factor authentication like one-time passwords (OTP) by using SMS or authenticator apps.
- DAST must get a cookie, or a local or session storage, with a sufficiently random value.
The [authentication debug output](#configure-the-authentication-debug-output) can be helpful for troubleshooting SSO authentication
with DAST.
### Log in using automatic detection of the login form
By providing a `DAST_USERNAME`, `DAST_PASSWORD`, and `DAST_AUTH_URL`, DAST attempts to authenticate to the
target application by locating the login form based on a determination about whether or not the form contains username or password fields.
Automatic detection is "best-effort", and depending on the application being scanned may provide either a resilient login experience or one that fails to authenticate the user.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If a form contains a username and password field, `DAST_USERNAME` and `DAST_PASSWORD` is inputted into the respective fields, the form submit button is selected and the user is logged in.
1. If a form contains only a username field, it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the username field and the form submit button is selected.
1. The subsequent pages loads where it is expected that a form exists and contains a password field. If found, `DAST_PASSWORD` is inputted, form submit button is selected and the user is logged in.
### Log in using explicit selection of the login form
By providing a `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, and `DAST_SUBMIT_FIELD`, in addition to the fields required for automatic login,
DAST attempts to authenticate to the target application by locating the login form based on the selectors provided.
Most applications benefit from this approach to authentication.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If the `DAST_FIRST_SUBMIT_FIELD` is not defined, then `DAST_USERNAME` is inputted into `DAST_USERNAME_FIELD`, `DAST_PASSWORD` is inputted into `DAST_PASSWORD_FIELD`, `DAST_SUBMIT_FIELD` is selected and the user is logged in.
1. If the `DAST_FIRST_SUBMIT_FIELD` is defined, then it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the `DAST_USERNAME_FIELD` field and the `DAST_FIRST_SUBMIT_FIELD` is selected.
1. The subsequent pages loads where the `DAST_PASSWORD` is inputted into the `DAST_PASSWORD_FIELD` field, the `DAST_SUBMIT_FIELD` is selected and the user is logged in.
### Verifying successful login
Once the login form has been submitted, DAST determines if the login was successful. Unsuccessful attempts at authentication cause the scan to halt.
Following the submission of the login form, authentication is determined to be unsuccessful when:
- A `400` or `500` series HTTP response status code is returned.
- A new cookie/browser storage value determined to be sufficiently random has not been set.
In addition to these checks, the user can configure their own verification checks.
Each of the following checks can be used in conjunction with one another, if none are configured by default the presence of a login form is checked.
#### Verifying based on the URL
When `DAST_AUTH_VERIFICATION_URL` is configured, the URL displayed in the browser tab post login form submission is directly compared to the URL in the CI/CD variable.
If these are not exactly the same, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler
...
DAST_AUTH_VERIFICATION_URL: "https://example.com/user/welcome"
```
#### Verify based on presence of an element
When `DAST_AUTH_VERIFICATION_SELECTOR` is configured, the page displayed in the browser tab is searched for an element described by the selector in the CI/CD variable.
If no element is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler
...
DAST_AUTH_VERIFICATION_SELECTOR: "css:.welcome-user"
```
#### Verify based on presence of a login form
When `DAST_AUTH_VERIFICATION_LOGIN_FORM` is configured, the page displayed in the browser tab is searched for a form that is detected to be a login form.
If any such form is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler
...
DAST_AUTH_VERIFICATION_LOGIN_FORM: "true"
```
### View the login form
Many web applications show the user the login form in a pop-up (modal) window.
For these applications, navigating to the form requires both:
- A starting URL.
- A list of elements to select to display the modal window.
When `DAST_BROWSER_PATH_TO_LOGIN_FORM` is present, like in this example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://my.site.com"
DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler
...
DAST_AUTH_URL: "https://my.site.com/admin"
DAST_BROWSER_PATH_TO_LOGIN_FORM: "css:.navigation-menu,css:.login-menu-item"
```
DAST performs these actions:
1. Load the `DAST_AUTH_URL` page, such as `https://my.site.com/admin`.
1. After the page loads, DAST selects elements found by the selectors described
in `DAST_BROWSER_PATH_TO_LOGIN_FORM`. This example opens the navigation menu
and selects the login menu, to display the login modal window.
1. To continue the authentication process, DAST fills in the username and password
on the login form.
### Configure the authentication debug output
It is often difficult to understand the cause of an authentication failure when running DAST in a CI/CD pipeline.
To assist users in debugging authentication issues, a debug report can be generated and saved as a job artifact.
This HTML report contains all steps made during the login process, along with HTTP requests and responses, the Document Object Model (DOM) and screenshots.
![dast-auth-report](img/dast_auth_report.jpg)
An example configuration where the authentication debug report is exported may look like the following:
```yaml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler
...
DAST_AUTH_REPORT: "true"
artifacts:
paths: [gl-dast-debug-auth-report.html]
when: always
```
### Selectors
Selectors are used by CI/CD variables to specify the location of an element displayed on a page in a browser.
Selectors have the format `type`:`search string`. The crawler searches for the selector using the search string based on the type.
| Selector type | Example | Description |
| ------------- | ---------------------------------- | ----------- |
| `css` | `css:.password-field` | Searches for a HTML element having the supplied CSS selector. Selectors should be as specific as possible for performance reasons. |
| `id` | `id:element` | Searches for an HTML element with the provided element ID. |
| `name` | `name:element` | Searches for an HTML element with the provided element name. |
| `xpath` | `xpath://input[@id="my-button"]/a` | Searches for a HTML element with the provided XPath. Note that XPath searches are expected to be less performant than other searches. |
| None provided | `a.click-me` | Defaults to searching using a CSS selector. |
#### Find selectors with Google Chrome
Chrome DevTools element selector tool is an effective way to find a selector.
1. Open Chrome and navigate to the page where you would like to find a selector, for example, the login page for your site.
1. Open the `Elements` tab in Chrome DevTools with the keyboard shortcut `Command + Shift + c` in macOS or `Ctrl + Shift + c` in Windows.
1. Select the `Select an element in the page to select it` tool.
![search-elements](img/dast_auth_browser_scan_search_elements.png)
1. Select the field on your page that you would like to know the selector for.
1. Once the tool is active, highlight a field you wish to view the details of.
![highlight](img/dast_auth_browser_scan_highlight.png)
1. Once highlighted, you can see the element's details, including attributes that would make a good candidate for a selector.
In this example, the `id="user_login"` appears to be a good candidate. You can use this as a selector as the DAST username field by setting
`DAST_USERNAME_FIELD: "id:user_login"`.
#### Choose the right selector
Judicious choice of selector leads to a scan that is resilient to the application changing.
In order of preference, it is recommended to choose as selectors:
- `id` fields. These are generally unique on a page, and rarely change.
- `name` fields. These are generally unique on a page, and rarely change.
- `class` values specific to the field, such as the selector `"css:.username"` for the `username` class on the username field.
- Presence of field specific data attributes, such as the selector, `"css:[data-username]"` when the `data-username` field has any value on the username field.
- Multiple `class` hierarchy values, such as the selector `"css:.login-form .username"` when there are multiple elements with class `username` but only one nested inside the element with the class `login-form`.
When using selectors to locate specific fields we recommend you avoid searching on:
- Any `id`, `name`, `attribute`, `class` or `value` that is dynamically generated.
- Generic class names, such as `column-10` and `dark-grey`.
- XPath searches as they are less performant than other selector searches.
- Unscoped searches, such as those beginning with `css:*` and `xpath://*`.
### Bleeding-edge vulnerability definitions
ZAP first creates rules in the `alpha` class. After a testing period with
@ -1147,9 +903,7 @@ and DAST site profiles are included in the [audit log](../../../administration/a
The DAST tool outputs a `gl-dast-report.json` report file containing details of the scan and its results.
This file is included in the job's artifacts. JSON is the default format, but
you can output the report in Markdown, HTML, and XML formats. To specify an alternative
format, use a [CI/CD variable](#available-cicd-variables). You can also use a CI/CD variable
to configure the job to output the `gl-dast-debug-auth-report.html` file which helps when debugging
authentication issues.
format, use a [CI/CD variable](#available-cicd-variables).
For details of the report's schema, see the [schema for DAST reports](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dast-report-format.json). Example reports can be found in the
[DAST repository](https://gitlab.com/gitlab-org/security-products/dast/-/tree/main/test/end-to-end/expect).

View File

@ -67,7 +67,7 @@ module API
package_file = Packages::Helm::PackageFilesFinder.new(project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent!
track_package_event('pull_package', :helm, project: project, namespace: project.namespace)
track_package_event('pull_package', :helm, project: project, namespace: project.namespace, property: 'i_package_helm_user')
present_package_file!(package_file)
end
@ -110,7 +110,8 @@ module API
package, chart_params.merge(build: current_authenticated_job)
).execute
track_package_event('push_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace)
track_package_event('push_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace,
property: 'i_package_helm_user')
::Packages::Helm::ExtractionWorker.perform_async(params[:channel], chart_package_file.id) # rubocop:disable CodeReuse/Worker

View File

@ -78,10 +78,18 @@ module API
end
end
def track_package_event(event_name, scope, **args)
::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute
def track_package_event(action, scope, **args)
::Packages::CreateEventService.new(nil, current_user, event_name: action, scope: scope).execute
category = args.delete(:category) || self.options[:for].name
::Gitlab::Tracking.event(category, event_name.to_s, **args)
event_name = "i_package_#{scope}_user"
::Gitlab::Tracking.event(
category,
action.to_s,
property: event_name,
label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly',
context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context],
**args
)
end
def present_package_file!(package_file, supports_direct_download: true)

View File

@ -33,7 +33,7 @@ module API
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
track_package_event('pull_package', package, category: 'API::NpmPackages', project: project, namespace: project.namespace)
track_package_event('pull_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace)
present_package_file!(package_file)
end

View File

@ -123,7 +123,7 @@ module API
package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
track_package_event('pull_package', :pypi)
track_package_event('pull_package', :pypi, namespace: group, project: package.project)
present_package_file!(package_file, supports_direct_download: true)
end

View File

@ -20,6 +20,13 @@ module Gitlab
def to_context
SnowplowTracker::SelfDescribingJson.new(SCHEMA_URL, @payload)
end
def to_h
{
schema: SCHEMA_URL,
data: @payload
}
end
end
end
end

View File

@ -233,7 +233,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it_behaves_like 'a successful manifest pull'
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest'
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
context 'with workhorse response' do
let(:pull_response) { { status: :success, manifest: nil, from_cache: false } }
@ -303,7 +303,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
it_behaves_like 'a successful blob pull'
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache', false
context 'when cache entry does not exist' do
let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
@ -387,7 +387,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
group.add_guest(user)
end
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob', false
it 'creates a blob' do
expect { subject }.to change { group.dependency_proxy_blobs.count }.by(1)
@ -445,7 +445,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
group.add_guest(user)
end
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest'
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
it_behaves_like 'with invalid path'
context 'with no existing manifest' do

View File

@ -114,7 +114,7 @@ RSpec.describe Groups::Registry::RepositoriesController do
it_behaves_like 'with name parameter'
it_behaves_like 'a package tracking event', described_class.name, 'list_repositories'
it_behaves_like 'a package tracking event', described_class.name, 'list_repositories', false
context 'with project in subgroup' do
let_it_be(:test_group) { create(:group, parent: group) }

View File

@ -683,4 +683,16 @@ RSpec.describe ApplicationHelper do
end
end
end
describe 'stylesheet_link_tag_defer' do
it 'uses print stylesheet by default' do
expect(helper.stylesheet_link_tag_defer('test')).to eq( '<link rel="stylesheet" media="print" href="/stylesheets/test.css" />')
end
it 'uses regular stylesheet when no_startup_css param present' do
allow(helper.controller).to receive(:params).and_return({ no_startup_css: '' })
expect(helper.stylesheet_link_tag_defer('test')).to eq( '<link rel="stylesheet" media="screen" href="/stylesheets/test.css" />')
end
end
end

View File

@ -238,4 +238,26 @@ RSpec.describe API::Helpers::PackagesHelpers do
end
end
end
describe '#track_package_event' do
before do
allow(helper).to receive(:current_user).and_return(user)
end
it_behaves_like 'Snowplow event tracking with RedisHLL context' do
let(:action) { 'push_package' }
let(:scope) { :terraform_module }
let(:category) { described_class.name }
let(:namespace) { project.namespace }
let(:user) { project.creator }
let(:feature_flag_name) { nil }
let(:label) { 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly' }
let(:property) { 'i_package_terraform_module_user' }
subject(:package_action) do
args = { category: category, namespace: namespace, user: user, project: project }
helper.track_package_event(action, scope, **args)
end
end
end
end

View File

@ -14,7 +14,10 @@ RSpec.describe API::ComposerPackages do
let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: project.namespace, user: user, property: 'i_package_composer_user' }
end
let(:headers) { {} }
using RSpec::Parameterized::TableSyntax
@ -491,7 +494,6 @@ RSpec.describe API::ComposerPackages do
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
@ -501,6 +503,10 @@ RSpec.describe API::ComposerPackages do
include_context 'Composer user type', params[:user_role], params[:member] do
if params[:expected_status] == :success
let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: project.namespace, property: 'i_package_composer_user' }
end
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
else
it_behaves_like 'not a package tracking event'

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe API::ConanInstancePackages do
let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' } }
include_context 'conan api setup'

View File

@ -5,7 +5,6 @@ RSpec.describe API::ConanProjectPackages do
include_context 'conan api setup'
let(:project_id) { project.id }
let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } }
describe 'GET /api/v4/projects/:id/packages/conan/v1/ping' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/ping" }

View File

@ -19,7 +19,7 @@ RSpec.describe API::GenericPackages do
let(:user) { personal_access_token.user }
let(:ci_build) { create(:ci_build, :running, user: user, project: project) }
let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { user: user, project: project, namespace: project.namespace, property: 'i_package_generic_user' } }
def auth_header
return {} if user_role == :anonymous
@ -408,8 +408,6 @@ RSpec.describe API::GenericPackages do
end
context 'event tracking' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
subject { upload_file(params, workhorse_headers.merge(personal_access_token_header)) }
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
@ -645,8 +643,6 @@ RSpec.describe API::GenericPackages do
end
context 'event tracking' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
before do
project.add_developer(user)
end

View File

@ -35,7 +35,9 @@ RSpec.describe API::GroupContainerRepositories do
describe 'GET /groups/:id/registry/repositories' do
let(:url) { "/groups/#{group.id}/registry/repositories" }
let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } }
let(:snowplow_gitlab_standard_context) do
{ user: api_user, namespace: group, property: 'i_package_container_user' }
end
subject { get api(url, api_user) }

View File

@ -17,6 +17,8 @@ RSpec.describe API::HelmPackages do
let_it_be(:package_file2_2) { create(:helm_package_file, package: package2, file_sha256: 'file2', file_name: 'filename2.tgz', channel: 'test', description: 'hello from test channel') }
let_it_be(:other_package) { create(:npm_package, project: project) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_helm_user' } }
describe 'GET /api/v4/projects/:id/packages/helm/:channel/index.yaml' do
let(:project_id) { project.id }
let(:channel) { 'stable' }
@ -63,7 +65,6 @@ RSpec.describe API::HelmPackages do
with_them do
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
before do
project.update!(visibility: visibility.to_s)
@ -74,8 +75,6 @@ RSpec.describe API::HelmPackages do
end
context 'with access to package registry for everyone' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
before do
project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE)
project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
@ -152,7 +151,6 @@ RSpec.describe API::HelmPackages do
let(:params) { { chart: temp_file(file_name) } }
let(:file_key) { :chart }
let(:send_rewritten_field) { true }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
subject do
workhorse_finalize(

View File

@ -22,7 +22,8 @@ RSpec.describe API::MavenPackages do
let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } }
let(:package_name) { 'com/example/my-app' }
let(:headers) { workhorse_headers }
let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
@ -98,8 +99,6 @@ RSpec.describe API::MavenPackages do
context 'with jar file' do
let_it_be(:package_file) { jar_file }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
end
end
@ -900,6 +899,8 @@ RSpec.describe API::MavenPackages do
it_behaves_like 'package workhorse uploads'
context 'event tracking' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_maven_user' } }
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
context 'when the package file fails to be created' do

View File

@ -55,7 +55,6 @@ RSpec.describe API::NpmProjectPackages do
end
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
let(:package_file) { package.package_files.first }
let(:headers) { {} }
@ -215,7 +214,7 @@ RSpec.describe API::NpmProjectPackages do
let_it_be(:version) { '1.2.3' }
let(:params) { upload_params(package_name: package_name, package_version: version) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_npm_user' } }
shared_examples 'handling upload with different authentications' do
context 'with access token' do

View File

@ -12,6 +12,7 @@ RSpec.describe API::NugetGroupPackages do
let_it_be(:deploy_token) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) }
let(:snowplow_gitlab_standard_context) { { namespace: project.group, property: 'i_package_nuget_user' } }
let(:target_type) { 'groups' }
shared_examples 'handling all endpoints' do
@ -46,7 +47,6 @@ RSpec.describe API::NugetGroupPackages do
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: subgroup) }
let(:target) { subgroup }
let(:snowplow_gitlab_standard_context) { { namespace: subgroup } }
it_behaves_like 'handling all endpoints'
@ -58,7 +58,7 @@ RSpec.describe API::NugetGroupPackages do
context 'a group' do
let(:target) { group }
let(:snowplow_gitlab_standard_context) { { namespace: group } }
let(:snowplow_gitlab_standard_context) { { namespace: target, property: 'i_package_nuget_user' } }
it_behaves_like 'handling all endpoints'

View File

@ -13,6 +13,7 @@ RSpec.describe API::NugetProjectPackages do
let(:target) { project }
let(:target_type) { 'projects' }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_nuget_user' } }
shared_examples 'accept get request on private project with access to package registry for everyone' do
subject { get api(url) }
@ -28,9 +29,7 @@ RSpec.describe API::NugetProjectPackages do
describe 'GET /api/v4/projects/:id/packages/nuget' do
let(:url) { "/projects/#{target.id}/packages/nuget/index.json" }
it_behaves_like 'handling nuget service requests' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
end
it_behaves_like 'handling nuget service requests'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
@ -58,9 +57,7 @@ RSpec.describe API::NugetProjectPackages do
describe 'GET /api/v4/projects/:id/packages/nuget/query' do
let(:url) { "/projects/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" }
it_behaves_like 'handling nuget search requests' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
end
it_behaves_like 'handling nuget search requests'
it_behaves_like 'accept get request on private project with access to package registry for everyone' do
let_it_be(:query_parameters) { { q: 'query', take: 5, skip: 0, prerelease: true } }
@ -152,7 +149,6 @@ RSpec.describe API::NugetProjectPackages do
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
subject { get api(url), headers: headers }

View File

@ -33,7 +33,10 @@ RSpec.describe API::ProjectContainerRepositories do
let(:method) { :get }
let(:params) { {} }
let(:snowplow_gitlab_standard_context) { { user: api_user, project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) do
{ user: api_user, project: project, namespace: project.namespace,
property: 'i_package_container_user' }
end
before_all do
project.add_maintainer(maintainer)
@ -414,6 +417,9 @@ RSpec.describe API::ProjectContainerRepositories do
context 'for developer', :snowplow do
let(:api_user) { developer }
let(:service_ping_context) do
[Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: 'i_package_container_user').to_h]
end
context 'when there are multiple tags' do
before do
@ -427,7 +433,10 @@ RSpec.describe API::ProjectContainerRepositories do
subject
expect(response).to have_gitlab_http_status(:ok)
expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace)
expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project,
user: api_user, namespace: project.namespace.reload,
label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly',
property: 'i_package_container_user', context: service_ping_context)
end
end
@ -443,7 +452,10 @@ RSpec.describe API::ProjectContainerRepositories do
subject
expect(response).to have_gitlab_http_status(:ok)
expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace)
expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project,
user: api_user, namespace: project.namespace.reload,
label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly',
property: 'i_package_container_user', context: service_ping_context)
end
end
end

View File

@ -14,6 +14,7 @@ RSpec.describe API::PypiPackages do
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } }
let(:headers) { {} }
@ -25,7 +26,7 @@ RSpec.describe API::PypiPackages do
describe 'GET /api/v4/groups/:id/-/packages/pypi/simple' do
let(:url) { "/groups/#{group.id}/-/packages/pypi/simple" }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } }
it_behaves_like 'pypi simple index API endpoint'
it_behaves_like 'rejects PyPI access with unknown group id'
@ -63,7 +64,7 @@ RSpec.describe API::PypiPackages do
describe 'GET /api/v4/projects/:id/packages/pypi/simple' do
let(:package_name) { package.name }
let(:url) { "/projects/#{project.id}/packages/pypi/simple" }
let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group } }
let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group, property: 'i_package_pypi_user' } }
it_behaves_like 'pypi simple index API endpoint'
it_behaves_like 'rejects PyPI access with unknown project id'
@ -81,13 +82,13 @@ RSpec.describe API::PypiPackages do
context 'simple package API endpoint' do
let_it_be(:package) { create(:pypi_package, project: project) }
let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group, property: 'i_package_pypi_user' } }
subject { get api(url), headers: headers }
describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do
let(:package_name) { package.name }
let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package_name}" }
let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group } }
it_behaves_like 'pypi simple API endpoint'
it_behaves_like 'rejects PyPI access with unknown group id'
@ -125,7 +126,7 @@ RSpec.describe API::PypiPackages do
describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
let(:package_name) { package.name }
let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package_name}" }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } }
it_behaves_like 'pypi simple API endpoint'
it_behaves_like 'rejects PyPI access with unknown project id'
@ -202,7 +203,7 @@ RSpec.describe API::PypiPackages do
let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64, md5_digest: '1' * 32 } }
let(:params) { base_params.merge(content: temp_file(file_name)) }
let(:send_rewritten_field) { true }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_pypi_user' } }
subject do
workhorse_finalize(
@ -366,7 +367,6 @@ RSpec.describe API::PypiPackages do
describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do
let(:url) { "/groups/#{group.id}/-/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
let(:snowplow_gitlab_standard_context) { {} }
it_behaves_like 'pypi file download endpoint'
it_behaves_like 'rejects PyPI access with unknown group id'
@ -375,7 +375,6 @@ RSpec.describe API::PypiPackages do
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
it_behaves_like 'pypi file download endpoint'
it_behaves_like 'rejects PyPI access with unknown project id'

View File

@ -136,7 +136,7 @@ RSpec.describe API::RpmProjectPackages do
end
describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, property: 'i_package_rpm_user' } }
let(:url) { "/projects/#{project.id}/packages/rpm/#{package_file_id}/#{package_name}" }
subject { get api(url), headers: headers }
@ -148,7 +148,10 @@ RSpec.describe API::RpmProjectPackages do
end
describe 'POST /api/v4/projects/:project_id/packages/rpm' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, user: user } }
let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: group, user: user, property: 'i_package_rpm_user' }
end
let(:url) { "/projects/#{project.id}/packages/rpm" }
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm') }

View File

@ -15,7 +15,7 @@ RSpec.describe API::RubygemPackages do
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let_it_be(:headers) { {} }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_rubygems_user' } }
let(:tokens) do
{
@ -164,7 +164,7 @@ RSpec.describe API::RubygemPackages do
with_them do
let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
let(:headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_rubygems_user' } }
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
@ -323,7 +323,7 @@ RSpec.describe API::RubygemPackages do
let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user, property: 'i_package_rubygems_user' } }
let(:snowplow_user) do
case token_type
when :deploy_token

View File

@ -418,7 +418,8 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
{
project: project,
user: user_role == :anonymous ? nil : user,
namespace: project.namespace
namespace: project.namespace,
property: 'i_package_terraform_module_user'
}
end
@ -583,7 +584,10 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
with_them do
let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } }
let(:snowplow_gitlab_standard_context) do
{ project: project, namespace: project.namespace, user: snowplow_user, property: 'i_package_terraform_module_user' }
end
let(:snowplow_user) do
case token_type
when :deploy_token

View File

@ -28,6 +28,10 @@ RSpec.shared_context 'conan api setup' do
)
end
let(:snowplow_gitlab_standard_context) do
{ user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' }
end
before do
project.add_developer(user)
allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)

View File

@ -17,6 +17,7 @@ RSpec.shared_context 'npm api setup' do
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:package_name) { package.name }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_npm_user' } }
before do
# create a duplicated package without triggering model validation errors

View File

@ -16,12 +16,14 @@
RSpec.shared_examples 'Snowplow event tracking' do |overrides: {}|
let(:extra) { {} }
it 'is not emitted if FF is disabled' do
stub_feature_flags(feature_flag_name => false)
if try(:feature_flag_name)
it 'is not emitted if FF is disabled' do
stub_feature_flags(feature_flag_name => false)
subject
subject
expect_no_snowplow_event(category: category, action: action)
expect_no_snowplow_event(category: category, action: action)
end
end
it 'is emitted' do

View File

@ -593,7 +593,7 @@ RSpec.shared_examples 'delete package endpoint' do
project.add_maintainer(user)
end
it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'delete_package'
it_behaves_like 'a package tracking event', 'API::ConanPackages', 'delete_package'
it 'deletes a package' do
expect { subject }.to change { Packages::Package.count }.from(2).to(1)
@ -708,7 +708,7 @@ RSpec.shared_examples 'package file download endpoint' do
context 'tracking the conan_package.tgz download' do
let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'pull_package'
it_behaves_like 'a package tracking event', 'API::ConanPackages', 'pull_package'
end
end
@ -781,7 +781,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'push_package'
it_behaves_like 'a package tracking event', 'API::ConanPackages', 'push_package'
end
end

View File

@ -495,7 +495,7 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace, property: 'i_package_nuget_user' } }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))

View File

@ -142,15 +142,26 @@ RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: fa
end
end
RSpec.shared_examples 'a package tracking event' do |category, action|
RSpec.shared_examples 'a package tracking event' do |category, action, service_ping_context = true|
before do
stub_feature_flags(collect_package_events: true)
end
let(:context) do
[Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
event: snowplow_gitlab_standard_context[:property]).to_h]
end
it "creates a gitlab tracking event #{action}", :snowplow, :aggregate_failures do
expect { subject }.to change { Packages::Event.count }.by(1)
expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
if service_ping_context
expect_snowplow_event(category: category, action: action,
label: "redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly",
context: context, **snowplow_gitlab_standard_context)
else
expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
end
end
end

View File

@ -273,7 +273,7 @@ RSpec.shared_examples 'pypi simple API endpoint' do
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, property: 'i_package_pypi_user' } }
it_behaves_like 'PyPI package versions', :developer, :success
end

View File

@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.3.0
github.com/aws/aws-sdk-go v1.44.136
github.com/aws/aws-sdk-go v1.44.142
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.4.2

View File

@ -227,8 +227,8 @@ github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.136 h1:J1KJJssa8pjU8jETYUxwRS37KTcxjACfKd9GK8t+5ZU=
github.com/aws/aws-sdk-go v1.44.136/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE=
github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.16.8 h1:gOe9UPR98XSf7oEJCcojYg+N2/jCRm4DdeIsP85pIyQ=
github.com/aws/aws-sdk-go-v2 v1.16.8/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw=