Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-21 06:12:55 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-21 06:12:55 +0300
commitb82d69110784e196facfbe3f8dfc8111393a5dac (patch)
treed0da7ee51cef64aa1256b16b19583d1f2b7624c5
parent551734207fa6241cf444b1154294e9b82831ae9a (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/helpers/ci/pipelines_helper.rb11
-rw-r--r--app/views/projects/pipelines/_info.html.haml79
-rw-r--r--doc/update/index.md6
-rw-r--r--doc/user/application_security/dast/checks/1004.1.md41
-rw-r--r--doc/user/application_security/dast/checks/16.1.md33
-rw-r--r--doc/user/application_security/dast/checks/16.2.md44
-rw-r--r--doc/user/application_security/dast/checks/16.3.md35
-rw-r--r--doc/user/application_security/dast/checks/16.4.md28
-rw-r--r--doc/user/application_security/dast/checks/16.5.md30
-rw-r--r--doc/user/application_security/dast/checks/614.1.md40
-rw-r--r--doc/user/application_security/dast/checks/693.1.md36
-rw-r--r--doc/user/application_security/dast/checks/index.md20
-rw-r--r--lib/gitlab/url_blocker.rb16
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/support/matchers/eventually_matcher.rb6
-rw-r--r--qa/qa/support/repeater.rb33
-rw-r--r--qa/qa/support/retrier.rb23
-rw-r--r--qa/qa/support/waiter.rb23
-rw-r--r--qa/spec/support/repeater_spec.rb75
-rw-r--r--qa/spec/support/retrier_spec.rb71
-rw-r--r--qa/spec/support/waiter_spec.rb35
-rw-r--r--spec/helpers/ci/pipelines_helper_spec.rb22
-rw-r--r--spec/models/project_spec.rb16
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb39
-rw-r--r--spec/validators/addressable_url_validator_spec.rb12
25 files changed, 562 insertions, 218 deletions
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index 6be46b40023..6104a1256d5 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -67,6 +67,17 @@ module Ci
]
end
+ def has_pipeline_badges?(pipeline)
+ pipeline.child? ||
+ pipeline.latest? ||
+ pipeline.merge_train_pipeline? ||
+ pipeline.has_yaml_errors? ||
+ pipeline.failure_reason? ||
+ pipeline.auto_devops_source? ||
+ pipeline.detached_merge_request_pipeline? ||
+ pipeline.stuck?
+ end
+
private
def warning_markdown(pipeline)
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 4a10f6aee1c..0bfdee088b4 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -17,45 +17,46 @@
- if @pipeline.queued_duration
= "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
- .well-segment.qa-pipeline-badges
- .icon-container
- = sprite_icon('flag')
- - if @pipeline.child?
- %span.js-pipeline-child.badge.badge-pill.gl-badge.sm.badge-primary.has-tooltip{ title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
- = s_('Pipelines|Child pipeline')
- = surround '(', ')' do
- = link_to s_('Pipelines|parent'), pipeline_path(@pipeline.triggered_by_pipeline), class: 'text-white text-underline'
- - if @pipeline.latest?
- %span.js-pipeline-url-latest.badge.badge-pill.gl-badge.sm.badge-success.has-tooltip{ title: _("Latest pipeline for the most recent commit on this branch") }
- latest
- - if @pipeline.merge_train_pipeline?
- %span.js-pipeline-url-train.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _("This is a merge train pipeline") }
- train
- - if @pipeline.has_yaml_errors?
- %span.js-pipeline-url-yaml.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
- yaml invalid
- - if @pipeline.failure_reason?
- %span.js-pipeline-url-failure.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.failure_reason }
- error
- - if @pipeline.auto_devops_source?
- - popover_title_text = html_escape(_('This pipeline makes use of a predefined CI/CD configuration enabled by %{b_open}Auto DevOps.%{b_close}')) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
- - popover_content_url = help_page_path('topics/autodevops/index.md')
- - popover_content_text = _('Learn more about Auto DevOps')
- %a.js-pipeline-url-autodevops.badge.badge-pill.gl-badge.sm.badge-info.autodevops-badge{ href: "#", tabindex: "0", role: "button", data: { container: "body",
- toggle: "popover",
- placement: "top",
- html: "true",
- triggers: "focus",
- title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>",
- content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
- } }
- Auto DevOps
- - if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
- detached
- - if @pipeline.stuck?
- %span.js-pipeline-url-stuck.badge.badge-pill.gl-badge.sm.badge-warning
- stuck
+ - if has_pipeline_badges?(@pipeline)
+ .well-segment.qa-pipeline-badges
+ .icon-container
+ = sprite_icon('flag')
+ - if @pipeline.child?
+ %span.js-pipeline-child.badge.badge-pill.gl-badge.sm.badge-primary.has-tooltip{ title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
+ = s_('Pipelines|Child pipeline')
+ = surround '(', ')' do
+ = link_to s_('Pipelines|parent'), pipeline_path(@pipeline.triggered_by_pipeline), class: 'text-white text-underline'
+ - if @pipeline.latest?
+ %span.js-pipeline-url-latest.badge.badge-pill.gl-badge.sm.badge-success.has-tooltip{ title: _("Latest pipeline for the most recent commit on this branch") }
+ latest
+ - if @pipeline.merge_train_pipeline?
+ %span.js-pipeline-url-train.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _("This is a merge train pipeline") }
+ train
+ - if @pipeline.has_yaml_errors?
+ %span.js-pipeline-url-yaml.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
+ yaml invalid
+ - if @pipeline.failure_reason?
+ %span.js-pipeline-url-failure.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.failure_reason }
+ error
+ - if @pipeline.auto_devops_source?
+ - popover_title_text = html_escape(_('This pipeline makes use of a predefined CI/CD configuration enabled by %{b_open}Auto DevOps.%{b_close}')) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
+ - popover_content_url = help_page_path('topics/autodevops/index.md')
+ - popover_content_text = _('Learn more about Auto DevOps')
+ %a.js-pipeline-url-autodevops.badge.badge-pill.gl-badge.sm.badge-info.autodevops-badge{ href: "#", tabindex: "0", role: "button", data: { container: "body",
+ toggle: "popover",
+ placement: "top",
+ html: "true",
+ triggers: "focus",
+ title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>",
+ content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
+ } }
+ Auto DevOps
+ - if @pipeline.detached_merge_request_pipeline?
+ %span.js-pipeline-url-mergerequest.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
+ detached
+ - if @pipeline.stuck?
+ %span.js-pipeline-url-stuck.badge.badge-pill.gl-badge.sm.badge-warning
+ stuck
.well-segment.branch-info
.icon-container.commit-icon
diff --git a/doc/update/index.md b/doc/update/index.md
index 973e111fe3c..ca44c22a10e 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -303,6 +303,12 @@ NOTE:
Specific information that follow related to Ruby and Git versions do not apply to [Omnibus installations](https://docs.gitlab.com/omnibus/)
and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with appropriate Ruby and Git versions and are not using system binaries for Ruby and Git. There is no need to install Ruby or Git when utilizing these two approaches.
+### 14.5.0
+
+When `make` is run, Gitaly builds are now created in `_build/bin` and no longer in the root directory of the source directory. If you
+are using a source install, update paths to these binaries in your init scripts by
+[following the documentation](upgrading_from_source.md#init-script).
+
### 14.4.0
Git 2.33.x and later is required. We recommend you use the
diff --git a/doc/user/application_security/dast/checks/1004.1.md b/doc/user/application_security/dast/checks/1004.1.md
new file mode 100644
index 00000000000..cbbcea1d34d
--- /dev/null
+++ b/doc/user/application_security/dast/checks/1004.1.md
@@ -0,0 +1,41 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# Sensitive cookie without `HttpOnly` attribute
+
+## Description
+
+The {cookie_name} cookie was transmitted in a `Set-Cookie` header without the `HttpOnly` attribute set.
+To prevent JavaScript being able to access the cookie value - usually via `document.cookies` - all
+cookies that are used for authorization or contain sensitive information should have the `HttpOnly` attribute
+set.
+
+## Remediation
+
+Most web application frameworks allow configuring how cookies are sent to user-agents. Consult your framework's
+documentation for more information on how to enable various security directives when assigning cookies to clients.
+
+If the application is assigning cookies via writing to the response headers directly, ensure all responses include
+the `HttpOnly` attribute. By enabling this protection, the application is able to mitigate the impact of
+certain Cross-Site Scripting (XSS) attacks.
+
+Example:
+
+```http
+Set-Cookie: {cookie_name}=<random secure value>; HttpOnly
+```
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 1004.1 | false | 1004 | Passive | Low |
+
+## Links
+
+- [owasp](https://owasp.org/www-community/HttpOnly)
+- [cwe](https://cwe.mitre.org/data/definitions/1004.html)
+- [Mozilla MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
diff --git a/doc/user/application_security/dast/checks/16.1.md b/doc/user/application_security/dast/checks/16.1.md
new file mode 100644
index 00000000000..bb030d2f9c4
--- /dev/null
+++ b/doc/user/application_security/dast/checks/16.1.md
@@ -0,0 +1,33 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# Missing Content-Type header
+
+## Description
+
+The `Content-Type` header ensures that user agents correctly interpret the data being received. Without this header
+being sent, the browser may misinterpret the data, leading to MIME confusion attacks. If an attacker were able
+to upload files that are accessible by using a browser, they could upload files that may be interpreted as
+HTML and so execute Cross-Site Scripting (XSS) attacks.
+
+## Remediation
+
+Ensure all resources return a proper `Content-Type` header that matches their format. As an example,
+when returning JavaScript files, the response header should be: `Content-Type: application/javascript`
+
+For added protection, we recommend that all resources return the `X-Content-Type-Options: nosniff`
+header to disable user agents from mis-interpreting resources.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 16.1 | true | 16 | Passive | Low |
+
+## Links
+
+- [cwe](https://cwe.mitre.org/data/definitions/16.html)
+- [Mozilla Blog on MIME Confusion attacks](https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/)
diff --git a/doc/user/application_security/dast/checks/16.2.md b/doc/user/application_security/dast/checks/16.2.md
new file mode 100644
index 00000000000..95461e8677d
--- /dev/null
+++ b/doc/user/application_security/dast/checks/16.2.md
@@ -0,0 +1,44 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# Server header exposes version information
+
+## Description
+
+The target website returns the `Server` header and version information of this website. By
+exposing these values, attackers may attempt to identify if the target software is vulnerable to known
+vulnerabilities, or catalog known sites running particular versions to exploit in the future when a
+vulnerability is identified in the particular version.
+
+## Remediation
+
+We recommend that the version information be removed from the `Server` header.
+
+Apache:
+For Apache based web sites, set the `ServerTokens` to `Prod` in the `httpd.conf` configuration file.
+
+NGINX:
+For NGINX based websites, set the `server_tokens` configuration value to `off` in the `nginx.conf` file.
+
+IIS:
+For IIS based websites version 10 and above you can use the `removeServerHeader` element to the `requestFiltering`
+section of the `Web.config` file.
+
+For all other server types, please consult your product's documentation on how to redact the version information from
+the `Server` header.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 16.2 | true | 16 | Passive | Low |
+
+## Links
+
+- [cwe](https://cwe.mitre.org/data/definitions/16.html)
+- [Apache ServerTokens](https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/)
+- [NGINX server_tokens](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens)
+- [IIS 10 Remove Server Header](https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/#attributes)
diff --git a/doc/user/application_security/dast/checks/16.3.md b/doc/user/application_security/dast/checks/16.3.md
new file mode 100644
index 00000000000..e4dcf3ece4b
--- /dev/null
+++ b/doc/user/application_security/dast/checks/16.3.md
@@ -0,0 +1,35 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# X-Powered-By header exposes version information
+
+## Description
+
+The target website returns the `X-Powered-By` header and version information of this website. By
+exposing these values, attackers may attempt to identify if the target software is vulnerable to known
+vulnerabilities, or catalog known sites running particular versions to exploit in the future when a
+vulnerability is identified in the particular version.
+
+## Remediation
+
+We recommend that the version information be removed from the `X-Powered-By` header.
+
+PHP:
+For PHP based web sites, set the `expose_php` option to `off` in the `php.ini` configuration file.
+
+For all other server types, please consult your product's documentation on how to redact the version
+information from the `X-Powered-By` header.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 16.3 | true | 16 | Passive | Low |
+
+## Links
+
+- [cwe](https://cwe.mitre.org/data/definitions/16.html)
+- [PHP expose_php](https://www.php.net/manual/en/ini.core.php#ini.expose-php)
diff --git a/doc/user/application_security/dast/checks/16.4.md b/doc/user/application_security/dast/checks/16.4.md
new file mode 100644
index 00000000000..c0161c910b0
--- /dev/null
+++ b/doc/user/application_security/dast/checks/16.4.md
@@ -0,0 +1,28 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# X-Backend-Server header exposes server information
+
+## Description
+
+The target website returns the `X-Backend-Server` header which includes potentially internal/hidden IP addresses
+or hostnames. By exposing these values, attackers may attempt to circumvent security proxies and access these
+hosts directly.
+
+## Remediation
+
+Consult your proxy/load balancer documentation or provider on how to disable revealing the
+`X-Backend-Server` header value.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 16.4 | true | 16 | Passive | Info |
+
+## Links
+
+- [cwe](https://cwe.mitre.org/data/definitions/16.html)
diff --git a/doc/user/application_security/dast/checks/16.5.md b/doc/user/application_security/dast/checks/16.5.md
new file mode 100644
index 00000000000..8a6f3cd8b6a
--- /dev/null
+++ b/doc/user/application_security/dast/checks/16.5.md
@@ -0,0 +1,30 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# AspNet Header(s) exposes version information
+
+## Description
+
+The target website returns AspNet header(s) and version information of this website. By
+exposing these values attackers may attempt to identify if the target software is vulnerable to known
+vulnerabilities, or catalog known sites running particular versions to exploit in the future when a
+vulnerability is identified in the particular version.
+
+## Remediation
+
+To remove the `X-AspNet-Version` header set `<httpRuntime enableVersionHeader="false" />` in the `<system.Web>`
+section of the `Web.config` file.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 16.5 | true | 16 | Passive | Low |
+
+## Links
+
+- [cwe](https://cwe.mitre.org/data/definitions/16.html)
+- [IIS Remove Unwanted Headers](https://techcommunity.microsoft.com/t5/iis-support-blog/remove-unwanted-http-response-headers/ba-p/369710)
diff --git a/doc/user/application_security/dast/checks/614.1.md b/doc/user/application_security/dast/checks/614.1.md
new file mode 100644
index 00000000000..74ac73935f1
--- /dev/null
+++ b/doc/user/application_security/dast/checks/614.1.md
@@ -0,0 +1,40 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# Sensitive cookie without `Secure` attribute
+
+## Description
+
+The {cookie_name} cookie was transmitted in a `Set-Cookie` response without the `Secure` attribute set.
+To prevent sensitive cookie values being accidentally transmitted over clear-text HTTP we
+recommended that cookies are declared with the `Secure` attribute.
+
+## Remediation
+
+Most web application frameworks allow configuring how cookies are sent to user agents. Consult your framework's
+documentation for more information on how to enable various security attributes when assigning cookies to clients.
+
+If the application is assigning cookies via writing to the response headers directly, ensure all responses include
+the `Secure` attribute. By enabling this protection, the application will no longer send sensitive cookies over
+HTTP.
+
+Example:
+
+```http
+Set-Cookie: {cookie_name}=<random secure value>; Secure
+```
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 614.1 | false | 614 | Passive | Low |
+
+## Links
+
+- [owasp](https://owasp.org/www-community/controls/SecureCookieAttribute)
+- [cwe](https://cwe.mitre.org/data/definitions/614.html)
+- [Mozilla MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
diff --git a/doc/user/application_security/dast/checks/693.1.md b/doc/user/application_security/dast/checks/693.1.md
new file mode 100644
index 00000000000..07cb368b39a
--- /dev/null
+++ b/doc/user/application_security/dast/checks/693.1.md
@@ -0,0 +1,36 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# Missing X-Content-Type-Options: nosniff
+
+## Description
+
+The `X-Content-Type-Options` header with the value `nosniff` ensures that user agents do not attempt to
+guess the format of the data being received. User Agents such as browsers, commonly attempt to guess
+what the resource type being requested is, through a process called MIME type sniffing.
+
+Without this header being sent, the browser may misinterpret the data, leading to MIME confusion attacks.
+If an attacker were able to upload files that are accessible by using a browser, they could upload files
+that could be interpreted as HTML and execute Cross-Site Scripting (XSS) attacks.
+
+## Remediation
+
+We recommend that the header and value of `X-Content-Type-Options: nosniff` be set server wide.
+This ensures any resources that are mistakenly missing a `Content-Type` value are not
+misinterpreted.
+
+## Details
+
+| ID | Aggregated | CWE | Type | Risk |
+|:---|:--------|:--------|:--------|:--------|
+| 693.1 | true | 693 | Passive | Low |
+
+## Links
+
+- [owasp](https://owasp.org/www-project-secure-headers/#x-content-type-options)
+- [cwe](https://cwe.mitre.org/data/definitions/693.html)
+- [Mozilla Blog on MIME Confusion attacks](https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/)
+- [Mozilla MDN on X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
diff --git a/doc/user/application_security/dast/checks/index.md b/doc/user/application_security/dast/checks/index.md
new file mode 100644
index 00000000000..f1a68387eb1
--- /dev/null
+++ b/doc/user/application_security/dast/checks/index.md
@@ -0,0 +1,20 @@
+---
+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/engineering/ux/technical-writing/#assignments
+---
+
+# DAST browser-based crawler vulnerability checks **(ULTIMATE)**
+
+The [DAST browser-based crawler](../browser_based.md) provides a number of vulnerability checks that are used to scan for vulnerabilities in the site under test.
+
+| ID | Check | Severity | Type |
+|:---|:------|:---------|:-----|
+| [1004.1](1004.1.md) | Sensitive cookie without `HttpOnly` attribute | Low | Passive |
+| [16.1](16.1.md) | Missing Content-Type header | Low | Passive |
+| [16.2](16.2.md) | Server header exposes version information | Low | Passive |
+| [16.3](16.3.md) | X-Powered-By header exposes version information | Low | Passive |
+| [16.4](16.4.md) | X-Backend-Server header exposes server information | Info | Passive |
+| [16.5](16.5.md) | AspNet Header(s) exposes version information | Low | Passive |
+| [614.1](614.1.md) | Sensitive cookie without `Secure` attribute | Low | Passive |
+| [693.1](693.1.md) | Missing X-Content-Type-Options: nosniff | Low | Passive |
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 10822f943b6..2c5d76ba41d 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -164,15 +164,21 @@ module Gitlab
end
def parse_url(url)
- raise Addressable::URI::InvalidURIError if multiline?(url)
-
- Addressable::URI.parse(url)
+ Addressable::URI.parse(url).tap do |parsed_url|
+ raise Addressable::URI::InvalidURIError if multiline_blocked?(parsed_url)
+ end
rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
raise BlockedUrlError, 'URI is invalid'
end
- def multiline?(url)
- CGI.unescape(url.to_s) =~ /\n|\r/
+ def multiline_blocked?(parsed_url)
+ url = parsed_url.to_s
+
+ return true if url =~ /\n|\r/
+ # Google Cloud Storage uses a multi-line, encoded Signature query string
+ return false if %w(http https).include?(parsed_url.scheme&.downcase)
+
+ CGI.unescape(url) =~ /\n|\r/
end
def validate_port(port, ports)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7fda4e73d2a..3dc87e4112b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -39693,9 +39693,6 @@ msgstr ""
msgid "ZenTaoIntegration|ZenTao user"
msgstr ""
-msgid "Zentao issues"
-msgstr ""
-
msgid "ZentaoIntegration|An error occurred while requesting data from the ZenTao service."
msgstr ""
@@ -39732,9 +39729,6 @@ msgstr ""
msgid "ZentaoIntegration|ZenTao issues"
msgstr ""
-msgid "ZentaoIntegration|Zentao issues"
-msgstr ""
-
msgid "Zoom meeting added"
msgstr ""
diff --git a/qa/qa/support/matchers/eventually_matcher.rb b/qa/qa/support/matchers/eventually_matcher.rb
index ff8adab424b..dedef8e6b98 100644
--- a/qa/qa/support/matchers/eventually_matcher.rb
+++ b/qa/qa/support/matchers/eventually_matcher.rb
@@ -59,8 +59,10 @@ module QA
def wait_and_check(actual, expectation_name)
attempt = 0
- QA::Runtime::Logger.debug("Running eventually matcher with '#{operator_msg}' operator")
- QA::Support::Retrier.retry_until(**@retry_args) do
+ QA::Runtime::Logger.debug(
+ "Running eventually matcher with '#{operator_msg}' operator with: #{@retry_args}"
+ )
+ QA::Support::Retrier.retry_until(**@retry_args, log: false) do
QA::Runtime::Logger.debug("evaluating expectation, attempt: #{attempt += 1}")
public_send(expectation_name, actual)
diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb
index b3a2472d702..22823afbb31 100644
--- a/qa/qa/support/repeater.rb
+++ b/qa/qa/support/repeater.rb
@@ -25,10 +25,26 @@ module QA
begin
while remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
- QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts && log
+ # start logging from the second attempt
+ if log && attempts == 1
+ msg = ["Retrying action with:"]
+ msg << "max_attempts: #{max_attempts};" if max_attempts
+ msg << "max_duration: #{max_duration};" if max_duration
+ msg << "reload_page: #{reload_page};" if reload_page
+ msg << "sleep_interval: #{sleep_interval};"
+ msg << "raise_on_failure: #{raise_on_failure};"
+ msg << "retry_on_exception: #{retry_on_exception}"
+
+ QA::Runtime::Logger.debug(msg.join(' '))
+ end
+
+ QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if log && max_attempts && attempts > 0
result = yield
- return result if result
+ if result
+ log_completion(log, attempts)
+ return result
+ end
sleep_and_reload_if_needed(sleep_interval, reload_page)
attempts += 1
@@ -54,6 +70,8 @@ module QA
raise WaitExceededError, "Wait condition not met after #{max_duration} #{'second'.pluralize(max_duration)}"
end
+ log_completion(log, attempts)
+
false
end
@@ -71,6 +89,17 @@ module QA
def remaining_time?(start, max_duration)
max_duration ? Time.now - start < max_duration : true
end
+
+ # Log completion if more than one attempt performed
+ #
+ # @param [Boolean] log
+ # @param [Integer] attempts
+ # @return [void]
+ def log_completion(log, attempts)
+ return unless log && attempts > 0
+
+ QA::Runtime::Logger.debug('ended retry')
+ end
end
end
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
index aa568d633fc..0439f546fb8 100644
--- a/qa/qa/support/retrier.rb
+++ b/qa/qa/support/retrier.rb
@@ -8,13 +8,6 @@ module QA
module_function
def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5, log: true)
- if log
- msg = ["with retry_on_exception: max_attempts: #{max_attempts}"]
- msg << "reload_page: #{reload_page}" if reload_page
- msg << "sleep_interval: #{sleep_interval}"
- QA::Runtime::Logger.debug(msg.join('; '))
- end
-
result = nil
repeat_until(
max_attempts: max_attempts,
@@ -29,7 +22,6 @@ module QA
# We set it to `true` so that it doesn't repeat if there's no exception
true
end
- QA::Runtime::Logger.debug("ended retry_on_exception") if log
result
end
@@ -46,20 +38,6 @@ module QA
# For backwards-compatibility
max_attempts = 3 if max_attempts.nil? && max_duration.nil?
- if log
- start_msg = ["with retry_until:"]
- start_msg << "max_attempts: #{max_attempts};" if max_attempts
- start_msg << "max_duration: #{max_duration};" if max_duration
- start_msg.push(*[
- "reload_page: #{reload_page};",
- "sleep_interval: #{sleep_interval};",
- "raise_on_failure: #{raise_on_failure};",
- "retry_on_exception: #{retry_on_exception}"
- ])
-
- QA::Runtime::Logger.debug(start_msg.join(' '))
- end
-
result = nil
repeat_until(
max_attempts: max_attempts,
@@ -72,7 +50,6 @@ module QA
) do
result = yield
end
- QA::Runtime::Logger.debug("ended retry_until") if log
result
end
diff --git a/qa/qa/support/waiter.rb b/qa/qa/support/waiter.rb
index 9ccc0d9484f..6dbbd197b01 100644
--- a/qa/qa/support/waiter.rb
+++ b/qa/qa/support/waiter.rb
@@ -7,20 +7,16 @@ module QA
module_function
- def wait_until(max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME, reload_page: nil, sleep_interval: 0.1, raise_on_failure: true, retry_on_exception: false, log: true)
- if log
- QA::Runtime::Logger.debug(
- <<~MSG.tr("\n", ' ')
- with wait_until: max_duration: #{max_duration};
- reload_page: #{reload_page};
- sleep_interval: #{sleep_interval};
- raise_on_failure: #{raise_on_failure}
- MSG
- )
- end
-
+ def wait_until(
+ max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME,
+ reload_page: nil,
+ sleep_interval: 0.1,
+ raise_on_failure: true,
+ retry_on_exception: false,
+ log: true
+ )
result = nil
- self.repeat_until(
+ repeat_until(
max_duration: max_duration,
reload_page: reload_page,
sleep_interval: sleep_interval,
@@ -30,7 +26,6 @@ module QA
) do
result = yield
end
- QA::Runtime::Logger.debug("ended wait_until") if log
result
end
diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb
index da8d6b18fb0..8109c2cce7b 100644
--- a/qa/spec/support/repeater_spec.rb
+++ b/qa/spec/support/repeater_spec.rb
@@ -380,34 +380,67 @@ RSpec.describe QA::Support::Repeater do
end
end
- it 'logs attempts' do
- attempted = false
-
- expect do
- subject.repeat_until(max_attempts: 1) do
- unless attempted
- attempted = true
- break false
- end
+ context 'with logging' do
+ before do
+ allow(QA::Runtime::Logger).to receive(:debug)
+ end
+ it 'skips logging single attempt with max_attempts' do
+ subject.repeat_until(max_attempts: 3) do
true
end
- end.to output(/Attempt number/).to_stdout_from_any_process
- end
- it 'allows logging to be silenced' do
- attempted = false
-
- expect do
- subject.repeat_until(max_attempts: 1, log: false) do
- unless attempted
- attempted = true
- break false
- end
+ expect(QA::Runtime::Logger).not_to have_received(:debug)
+ end
+ it 'skips logging single attempt with max_duration' do
+ subject.repeat_until(max_duration: 3) do
true
end
- end.not_to output.to_stdout_from_any_process
+
+ expect(QA::Runtime::Logger).not_to have_received(:debug)
+ end
+
+ it 'allows logging to be silenced' do
+ subject.repeat_until(max_attempts: 3, log: false, raise_on_failure: false) do
+ false
+ end
+
+ expect(QA::Runtime::Logger).not_to have_received(:debug)
+ end
+
+ it 'starts logging on subsequent attempts for max_duration' do
+ subject.repeat_until(max_duration: 0.3, sleep_interval: 0.1, raise_on_failure: false) do
+ false
+ end
+
+ aggregate_failures do
+ expect(QA::Runtime::Logger).to have_received(:debug).with(<<~MSG.strip).ordered.once
+ Retrying action with: max_duration: 0.3; sleep_interval: 0.1; raise_on_failure: false; retry_on_exception: false
+ MSG
+ expect(QA::Runtime::Logger).to have_received(:debug).with('ended retry').ordered.once
+ expect(QA::Runtime::Logger).not_to have_received(:debug).with(/Attempt number/)
+ end
+ end
+
+ it 'starts logging subsequent attempts for max_attempts' do
+ attempts = 0
+ subject.repeat_until(max_attempts: 4, raise_on_failure: false) do
+ next true if attempts == 2
+
+ attempts += 1
+ false
+ end
+
+ aggregate_failures do
+ expect(QA::Runtime::Logger).to have_received(:debug).with(<<~MSG.strip).ordered.once
+ Retrying action with: max_attempts: 4; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false
+ MSG
+ expect(QA::Runtime::Logger).to have_received(:debug).with('Attempt number 2').ordered.once
+ expect(QA::Runtime::Logger).to have_received(:debug).with('Attempt number 3').ordered.once
+ expect(QA::Runtime::Logger).to have_received(:debug).with('ended retry').ordered.once
+ end
+ end
end
end
end
diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb
index 9ad3e85fea9..bf7b0285512 100644
--- a/qa/spec/support/retrier_spec.rb
+++ b/qa/spec/support/retrier_spec.rb
@@ -1,42 +1,7 @@
# frozen_string_literal: true
RSpec.describe QA::Support::Retrier do
- before do
- logger = ::Logger.new $stdout
- logger.level = ::Logger::DEBUG
- QA::Runtime::Logger.logger = logger
- end
-
describe '.retry_until' do
- context 'when the condition is true' do
- it 'logs max attempts (3 by default)' do
- expect { subject.retry_until { true } }
- .to output(/with retry_until: max_attempts: 3; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process
- end
-
- it 'logs max duration' do
- expect { subject.retry_until(max_duration: 1) { true } }
- .to output(/with retry_until: max_duration: 1; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process
- end
-
- it 'logs the end' do
- expect { subject.retry_until { true } }
- .to output(/ended retry_until$/).to_stdout_from_any_process
- end
- end
-
- context 'when the condition is false' do
- it 'logs the start' do
- expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } }
- .to output(/with retry_until: max_duration: 0; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process
- end
-
- it 'logs the end' do
- expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } }
- .to output(/ended retry_until$/).to_stdout_from_any_process
- end
- end
-
context 'when max_duration and max_attempts are nil' do
it 'sets max attempts to 3 by default' do
expect(subject).to receive(:repeat_until).with(hash_including(max_attempts: 3))
@@ -62,35 +27,15 @@ RSpec.describe QA::Support::Retrier do
subject.retry_until
end
- end
-
- describe '.retry_on_exception' do
- context 'when the condition is true' do
- it 'logs max_attempts, reload_page, and sleep_interval parameters' do
- message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/
- expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { true } }
- .to output(message).to_stdout_from_any_process
- end
-
- it 'logs the end' do
- expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { true } }
- .to output(/ended retry_on_exception$/).to_stdout_from_any_process
- end
- end
- context 'when the condition is false' do
- it 'logs the start' do
- message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/
- expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { false } }
- .to output(message).to_stdout_from_any_process
- end
+ it 'allows logs to be silenced' do
+ expect(subject).to receive(:repeat_until).with(hash_including(log: false))
- it 'logs the end' do
- expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { false } }
- .to output(/ended retry_on_exception$/).to_stdout_from_any_process
- end
+ subject.retry_until(log: false)
end
+ end
+ describe '.retry_on_exception' do
it 'does not repeat if no exception is raised' do
loop_counter = 0
return_value = "test passed"
@@ -121,5 +66,11 @@ RSpec.describe QA::Support::Retrier do
subject.retry_on_exception
end
+
+ it 'allows logs to be silenced' do
+ expect(subject).to receive(:repeat_until).with(hash_including(log: false))
+
+ subject.retry_on_exception(log: false)
+ end
end
end
diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb
index d0b216b5dc1..c575a27bc35 100644
--- a/qa/spec/support/waiter_spec.rb
+++ b/qa/spec/support/waiter_spec.rb
@@ -1,40 +1,11 @@
# frozen_string_literal: true
RSpec.describe QA::Support::Waiter do
- before do
- logger = ::Logger.new $stdout
- logger.level = ::Logger::DEBUG
- QA::Runtime::Logger.logger = logger
- end
-
describe '.wait_until' do
- context 'when the condition is true' do
- it 'logs the start' do
- expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } }
- .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process
- end
-
- it 'logs the end' do
- expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } }
- .to output(/ended wait_until$/).to_stdout_from_any_process
- end
- end
-
- context 'when the condition is false' do
- it 'logs the start' do
- expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } }
- .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process
- end
-
- it 'logs the end' do
- expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } }
- .to output(/ended wait_until$/).to_stdout_from_any_process
- end
- end
-
it 'allows logs to be silenced' do
- expect { subject.wait_until(max_duration: 0, raise_on_failure: false, log: false) { false } }
- .not_to output.to_stdout_from_any_process
+ expect(subject).to receive(:repeat_until).with(hash_including(log: false))
+
+ subject.wait_until(log: false)
end
it 'sets max_duration to 60 by default' do
diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb
index 94b5e707d73..751bcc97582 100644
--- a/spec/helpers/ci/pipelines_helper_spec.rb
+++ b/spec/helpers/ci/pipelines_helper_spec.rb
@@ -71,4 +71,26 @@ RSpec.describe Ci::PipelinesHelper do
it { expect(has_gitlab_ci?).to eq(result) }
end
end
+
+ describe 'has_pipeline_badges?' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ subject { helper.has_pipeline_badges?(pipeline) }
+
+ context 'when pipeline has a badge' do
+ before do
+ pipeline.drop!(:config_error)
+ end
+
+ it 'shows pipeline badges' do
+ expect(subject).to eq(true)
+ end
+ end
+
+ context 'when pipeline has no badges' do
+ it 'shows pipeline badges' do
+ expect(subject).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 10220448936..3f5f0858178 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -424,8 +424,9 @@ RSpec.describe Project, factory_default: :keep do
end
include_context 'invalid urls'
+ include_context 'valid urls with CRLF'
- it 'does not allow urls with CR or LF characters' do
+ it 'does not allow URLs with unencoded CR or LF characters' do
project = build(:project)
aggregate_failures do
@@ -437,6 +438,19 @@ RSpec.describe Project, factory_default: :keep do
end
end
end
+
+ it 'allow URLs with CR or LF characters' do
+ project = build(:project)
+
+ aggregate_failures do
+ valid_urls_with_CRLF.each do |url|
+ project.import_url = url
+
+ expect(project).to be_valid
+ expect(project.errors).to be_empty
+ end
+ end
+ end
end
describe 'project pending deletion' do
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
index f3d227b6e2b..da1d6e0049c 100644
--- a/spec/support/shared_contexts/url_shared_context.rb
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -1,19 +1,32 @@
# frozen_string_literal: true
+RSpec.shared_context 'valid urls with CRLF' do
+ let(:valid_urls_with_CRLF) do
+ [
+ "http://example.com/pa%0dth",
+ "http://example.com/pa%0ath",
+ "http://example.com/pa%0d%0th",
+ "http://example.com/pa%0D%0Ath",
+ "http://gitlab.com/path?param=foo%0Abar",
+ "https://gitlab.com/path?param=foo%0Dbar",
+ "http://example.org:1024/path?param=foo%0D%0Abar",
+ "https://storage.googleapis.com/bucket/import_export_upload/import_file/57265/express.tar.gz?GoogleAccessId=hello@example.org&Signature=ABCD%0AEFGHik&Expires=1634663304"
+ ]
+ end
+end
+
RSpec.shared_context 'invalid urls' do
let(:urls_with_CRLF) do
- ["http://127.0.0.1:333/pa\rth",
- "http://127.0.0.1:333/pa\nth",
- "http://127.0a.0.1:333/pa\r\nth",
- "http://127.0.0.1:333/path?param=foo\r\nbar",
- "http://127.0.0.1:333/path?param=foo\rbar",
- "http://127.0.0.1:333/path?param=foo\nbar",
- "http://127.0.0.1:333/pa%0dth",
- "http://127.0.0.1:333/pa%0ath",
- "http://127.0a.0.1:333/pa%0d%0th",
- "http://127.0.0.1:333/pa%0D%0Ath",
- "http://127.0.0.1:333/path?param=foo%0Abar",
- "http://127.0.0.1:333/path?param=foo%0Dbar",
- "http://127.0.0.1:333/path?param=foo%0D%0Abar"]
+ [
+ "git://example.com/pa%0dth",
+ "git://example.com/pa%0ath",
+ "git://example.com/pa%0d%0th",
+ "http://example.com/pa\rth",
+ "http://example.com/pa\nth",
+ "http://example.com/pa\r\nth",
+ "http://example.com/path?param=foo\r\nbar",
+ "http://example.com/path?param=foo\rbar",
+ "http://example.com/path?param=foo\nbar"
+ ]
end
end
diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
index ec3ee9aa500..7e2cc2afa8a 100644
--- a/spec/validators/addressable_url_validator_spec.rb
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe AddressableUrlValidator do
describe 'validations' do
include_context 'invalid urls'
+ include_context 'valid urls with CRLF'
let(:validator) { described_class.new(attributes: [:link_url]) }
@@ -27,9 +28,20 @@ RSpec.describe AddressableUrlValidator do
expect(badge.errors.added?(:link_url, validator.options.fetch(:message))).to be true
end
+ it 'allows urls with encoded CR or LF characters' do
+ aggregate_failures do
+ valid_urls_with_CRLF.each do |url|
+ validator.validate_each(badge, :link_url, url)
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+
it 'does not allow urls with CR or LF characters' do
aggregate_failures do
urls_with_CRLF.each do |url|
+ badge = build(:badge, link_url: 'http://www.example.com')
validator.validate_each(badge, :link_url, url)
expect(badge.errors.added?(:link_url, 'is blocked: URI is invalid')).to be true