diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 3f1469156e6..803eeebfc45 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -171,7 +171,7 @@ graphql-schema-dump: graphql-schema-dump as-if-foss: extends: - graphql-schema-dump - - .frontend:rules:eslint-as-if-foss + - .frontend:rules:default-frontend-jobs-as-if-foss - .as-if-foss .frontend-test-base: diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index c4cd6cb611d..2c75c9a5a0f 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -46,7 +46,7 @@ files: - GITALY_SERVER_VERSION - lib/gitlab/setup_helper.rb - prefix: "gitaly-binaries-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" + prefix: "gitaly-binaries-${GITALY_SERVER_VERSION}-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" paths: - ${TMP_TEST_FOLDER}/gitaly/_build/bin/ - ${TMP_TEST_FOLDER}/gitaly/_build/deps/git/install/ diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 1a949adc6a2..e8b0174b8f6 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -9,6 +9,10 @@ "CiManualVariable", "CiProjectVariable" ], + "CommitSignature": [ + "GpgSignature", + "X509Signature" + ], "CurrentUserTodos": [ "BoardEpic", "Design", diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue index 4eab0cccb06..3717d8027c4 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue @@ -86,6 +86,7 @@ export default { :target="openInNewTab ? '_blank' : '_self'" :href="value.url" data-testid="uncompleted-learn-gitlab-link" + data-qa-selector="uncompleted_learn_gitlab_link" data-track-action="click_link" :data-track-label="actionLabelValue('trackLabel')" >{{ actionLabelValue('title') }} + + + {{ __('assign yourself') }} diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue index 0e4d4c74160..d83ae782e26 100644 --- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue @@ -91,6 +91,7 @@ export default {
diff --git a/app/graphql/types/commit_signature_interface.rb b/app/graphql/types/commit_signature_interface.rb new file mode 100644 index 00000000000..6b0c16e538a --- /dev/null +++ b/app/graphql/types/commit_signature_interface.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Types + module CommitSignatureInterface + include Types::BaseInterface + + graphql_name 'CommitSignature' + + description 'Represents signing information for a commit' + + field :verification_status, CommitSignatures::VerificationStatusEnum, + null: true, + description: 'Indicates verification status of the associated key or certificate.' + + field :commit_sha, GraphQL::Types::String, + null: true, + description: 'SHA of the associated commit.' + + field :project, Types::ProjectType, + null: true, + description: 'Project of the associated commit.' + + orphan_types Types::CommitSignatures::GpgSignatureType, + Types::CommitSignatures::X509SignatureType + + def self.resolve_type(object, context) + case object + when ::CommitSignatures::GpgSignature + Types::CommitSignatures::GpgSignatureType + when ::CommitSignatures::X509CommitSignature + Types::CommitSignatures::X509SignatureType + else + raise 'Unsupported commit signature type' + end + end + end +end diff --git a/app/graphql/types/commit_signatures/gpg_signature_type.rb b/app/graphql/types/commit_signatures/gpg_signature_type.rb new file mode 100644 index 00000000000..2a845fff3e2 --- /dev/null +++ b/app/graphql/types/commit_signatures/gpg_signature_type.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Types + module CommitSignatures + class GpgSignatureType < Types::BaseObject + graphql_name 'GpgSignature' + description 'GPG signature for a signed commit' + + implements Types::CommitSignatureInterface + + authorize :download_code + + field :user, Types::UserType, null: true, + description: 'User associated with the key.' + + field :gpg_key_user_name, GraphQL::Types::String, + null: true, + description: 'User name associated with the GPG key.' + + field :gpg_key_user_email, GraphQL::Types::String, + null: true, + description: 'User email associated with the GPG key.' + + field :gpg_key_primary_keyid, GraphQL::Types::String, + null: true, + description: 'ID of the GPG key.' + end + end +end diff --git a/app/graphql/types/commit_signatures/verification_status_enum.rb b/app/graphql/types/commit_signatures/verification_status_enum.rb new file mode 100644 index 00000000000..9df1b7abd82 --- /dev/null +++ b/app/graphql/types/commit_signatures/verification_status_enum.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + module CommitSignatures + class VerificationStatusEnum < BaseEnum + graphql_name 'VerificationStatus' + description 'Verification status of a GPG or X.509 signature for a commit.' + + ::CommitSignatures::GpgSignature.verification_statuses.each do |status, _| + value status.upcase, value: status, description: "#{status} verification status." + end + end + end +end + +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/commit_signatures/x509_signature_type.rb b/app/graphql/types/commit_signatures/x509_signature_type.rb new file mode 100644 index 00000000000..9ac96dbc015 --- /dev/null +++ b/app/graphql/types/commit_signatures/x509_signature_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module CommitSignatures + class X509SignatureType < Types::BaseObject + graphql_name 'X509Signature' + description 'X.509 signature for a signed commit' + + implements Types::CommitSignatureInterface + + authorize :download_code + + field :user, Types::UserType, null: true, + calls_gitaly: true, + description: 'User associated with the key.' + + field :x509_certificate, Types::X509CertificateType, + null: true, + description: 'Certificate used for the signature.' + end + end +end diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb index dfb02f29fb7..1ae88f98a9a 100644 --- a/app/graphql/types/commit_type.rb +++ b/app/graphql/types/commit_type.rb @@ -40,6 +40,11 @@ module Types field :web_path, type: GraphQL::Types::String, null: false, description: 'Web path of the commit.' + field :signature, type: Types::CommitSignatureInterface, + null: true, + calls_gitaly: true, + description: 'Signature of the commit.' + field :signature_html, type: GraphQL::Types::String, null: true, calls_gitaly: true, description: 'Rendered HTML of the commit signature.' diff --git a/app/graphql/types/x509_certificate_type.rb b/app/graphql/types/x509_certificate_type.rb new file mode 100644 index 00000000000..806aa441af7 --- /dev/null +++ b/app/graphql/types/x509_certificate_type.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + class X509CertificateType < Types::BaseObject + graphql_name 'X509Certificate' + description 'Represents an X.509 certificate.' + + field :certificate_status, GraphQL::Types::String, + null: false, + description: 'Indicates if the certificate is good or revoked.' + + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the certificate was saved.' + + field :email, GraphQL::Types::String, null: false, + description: 'Email associated with the cerificate.' + + field :id, GraphQL::Types::ID, null: false, description: 'ID of the certificate.' + + field :serial_number, GraphQL::Types::String, null: false, + description: 'Serial number of the certificate.' + + field :subject, GraphQL::Types::String, null: false, description: 'Subject of the certificate.' + + field :subject_key_identifier, GraphQL::Types::String, + null: false, + description: 'Subject key identifier of the certificate.' + + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the certificate was last updated.' + + field :x509_issuer, Types::X509IssuerType, null: false, + description: 'Issuer of the certificate.' + end +end + +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/x509_issuer_type.rb b/app/graphql/types/x509_issuer_type.rb new file mode 100644 index 00000000000..a5759e48ee0 --- /dev/null +++ b/app/graphql/types/x509_issuer_type.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + class X509IssuerType < Types::BaseObject + graphql_name 'X509Issuer' + description 'Issuer of an X.509 certificate.' + + field :created_at, Types::TimeType, null: true, + description: 'Timestamp of when the issuer was created.' + + field :crl_url, GraphQL::Types::String, null: true, + description: 'Certificate revokation list of the issuer.' + + field :id, GraphQL::Types::ID, null: true, description: 'ID of the issuer.' + + field :subject, GraphQL::Types::String, null: true, description: 'Subject of the issuer.' + + field :subject_key_identifier, GraphQL::Types::String, + null: true, + description: 'Subject key identifier of the issuer.' + + field :updated_at, Types::TimeType, null: true, + description: 'Timestamp of when the issuer was last updated.' + end +end + +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 53c29a3c8b4..50890489de2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -395,7 +395,8 @@ module ProjectsHelper membersPagePath: project_project_members_path(project), environmentsHelpPath: help_page_path('ci/environments/index'), featureFlagsHelpPath: help_page_path('operations/feature_flags'), - releasesHelpPath: help_page_path('user/project/releases/index') + releasesHelpPath: help_page_path('user/project/releases/index'), + infrastructureHelpPath: help_page_path('user/infrastructure/index') } end @@ -664,7 +665,8 @@ module ProjectsHelper containerRegistryAccessLevel: feature.container_registry_access_level, environmentsAccessLevel: feature.environments_access_level, featureFlagsAccessLevel: feature.feature_flags_access_level, - releasesAccessLevel: feature.releases_access_level + releasesAccessLevel: feature.releases_access_level, + infrastructureAccessLevel: feature.infrastructure_access_level } end diff --git a/app/models/commit_signatures/gpg_signature.rb b/app/models/commit_signatures/gpg_signature.rb index 1ce76b53da4..2ae59853520 100644 --- a/app/models/commit_signatures/gpg_signature.rb +++ b/app/models/commit_signatures/gpg_signature.rb @@ -49,5 +49,9 @@ module CommitSignatures Gitlab::Gpg::Commit.new(commit) end + + def user + gpg_key&.user + end end end diff --git a/app/policies/commit_signatures/gpg_signature_policy.rb b/app/policies/commit_signatures/gpg_signature_policy.rb new file mode 100644 index 00000000000..518a289c1f3 --- /dev/null +++ b/app/policies/commit_signatures/gpg_signature_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module CommitSignatures + class GpgSignaturePolicy < BasePolicy + delegate { @subject.project } + end +end diff --git a/app/policies/commit_signatures/x509_commit_signature_policy.rb b/app/policies/commit_signatures/x509_commit_signature_policy.rb new file mode 100644 index 00000000000..6b2477797fc --- /dev/null +++ b/app/policies/commit_signatures/x509_commit_signature_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module CommitSignatures + class X509CommitSignaturePolicy < BasePolicy + delegate { @subject.project } + end +end diff --git a/app/uploaders/object_storage/cdn.rb b/app/uploaders/object_storage/cdn.rb index e49e2780147..63c155f9210 100644 --- a/app/uploaders/object_storage/cdn.rb +++ b/app/uploaders/object_storage/cdn.rb @@ -12,8 +12,8 @@ module ObjectStorage UrlResult = Struct.new(:url, :used_cdn) - def cdn_enabled_url(project, ip_address) - if Feature.enabled?(:ci_job_artifacts_cdn, project) && use_cdn?(ip_address) + def cdn_enabled_url(ip_address) + if use_cdn?(ip_address) UrlResult.new(cdn_signed_url, true) else UrlResult.new(url, false) diff --git a/config/feature_flags/development/ci_job_artifacts_cdn.yml b/config/feature_flags/development/ci_job_artifacts_cdn.yml deleted file mode 100644 index 4a019312ee7..00000000000 --- a/config/feature_flags/development/ci_job_artifacts_cdn.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_job_artifacts_cdn -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98010 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373860 -milestone: '15.5' -type: development -group: group::pipeline execution -default_enabled: false diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md index ad345461776..ee7476ed6d4 100644 --- a/doc/administration/compliance.md +++ b/doc/administration/compliance.md @@ -48,9 +48,9 @@ settings and automation to ensure that whatever a compliance team has configured stays configured and working correctly. These features can help you automate compliance: -- [**Compliance frameworks**](../user/group/manage.md#compliance-frameworks) (for groups): Create a custom +- [**Compliance frameworks**](../user/group/compliance_frameworks.md) (for groups): Create a custom compliance framework at the group level to describe the type of compliance requirements any child project needs to follow. -- [**Compliance pipelines**](../user/group/manage.md#configure-a-compliance-pipeline) (for groups): Define a +- [**Compliance pipelines**](../user/group/compliance_frameworks.md#configure-a-compliance-pipeline) (for groups): Define a pipeline configuration to run for any projects with a given compliance framework. ## Audit management diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 8fe1ac5c696..4eca621b2da 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10939,6 +10939,7 @@ Represents a code quality degradation on the pipeline. | `message` | [`String`](#string) | Raw commit message. | | `sha` | [`String!`](#string) | SHA1 ID of the commit. | | `shortId` | [`String!`](#string) | Short SHA1 ID of the commit. | +| `signature` | [`CommitSignature`](#commitsignature) | Signature of the commit. | | `signatureHtml` | [`String`](#string) | Rendered HTML of the commit signature. | | `title` | [`String`](#string) | Title of the commit message. | | `titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. | @@ -12799,6 +12800,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | `replicationState` | [`ReplicationStateEnum`](#replicationstateenum) | Filters registries by their replication state. | | `verificationState` | [`VerificationStateEnum`](#verificationstateenum) | Filters registries by their verification state. | +### `GpgSignature` + +GPG signature for a signed commit. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `commitSha` | [`String`](#string) | SHA of the associated commit. | +| `gpgKeyPrimaryKeyid` | [`String`](#string) | ID of the GPG key. | +| `gpgKeyUserEmail` | [`String`](#string) | User email associated with the GPG key. | +| `gpgKeyUserName` | [`String`](#string) | User name associated with the GPG key. | +| `project` | [`Project`](#project) | Project of the associated commit. | +| `user` | [`UserCore`](#usercore) | User associated with the key. | +| `verificationStatus` | [`VerificationStatus`](#verificationstatus) | Indicates verification status of the associated key or certificate. | + ### `GrafanaIntegration` #### Fields @@ -20078,6 +20095,53 @@ Represents a weight widget. | `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | | `weight` | [`Int`](#int) | Weight of the work item. | +### `X509Certificate` + +Represents an X.509 certificate. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `certificateStatus` | [`String!`](#string) | Indicates if the certificate is good or revoked. | +| `createdAt` | [`Time!`](#time) | Timestamp of when the certificate was saved. | +| `email` | [`String!`](#string) | Email associated with the cerificate. | +| `id` | [`ID!`](#id) | ID of the certificate. | +| `serialNumber` | [`String!`](#string) | Serial number of the certificate. | +| `subject` | [`String!`](#string) | Subject of the certificate. | +| `subjectKeyIdentifier` | [`String!`](#string) | Subject key identifier of the certificate. | +| `updatedAt` | [`Time!`](#time) | Timestamp of when the certificate was last updated. | +| `x509Issuer` | [`X509Issuer!`](#x509issuer) | Issuer of the certificate. | + +### `X509Issuer` + +Issuer of an X.509 certificate. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `createdAt` | [`Time`](#time) | Timestamp of when the issuer was created. | +| `crlUrl` | [`String`](#string) | Certificate revokation list of the issuer. | +| `id` | [`ID`](#id) | ID of the issuer. | +| `subject` | [`String`](#string) | Subject of the issuer. | +| `subjectKeyIdentifier` | [`String`](#string) | Subject key identifier of the issuer. | +| `updatedAt` | [`Time`](#time) | Timestamp of when the issuer was last updated. | + +### `X509Signature` + +X.509 signature for a signed commit. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `commitSha` | [`String`](#string) | SHA of the associated commit. | +| `project` | [`Project`](#project) | Project of the associated commit. | +| `user` | [`UserCore`](#usercore) | User associated with the key. | +| `verificationStatus` | [`VerificationStatus`](#verificationstatus) | Indicates verification status of the associated key or certificate. | +| `x509Certificate` | [`X509Certificate`](#x509certificate) | Certificate used for the signature. | + ## Enumeration types Also called _Enums_, enumeration types are a special kind of scalar that @@ -21935,6 +21999,20 @@ Possible states of a user. | `STARTED` | Verification process is in progress. | | `SUCCEEDED` | Verification process finished successfully. | +### `VerificationStatus` + +Verification status of a GPG or X.509 signature for a commit. + +| Value | Description | +| ----- | ----------- | +| `MULTIPLE_SIGNATURES` | multiple_signatures verification status. | +| `OTHER_USER` | other_user verification status. | +| `SAME_USER_DIFFERENT_EMAIL` | same_user_different_email verification status. | +| `UNKNOWN_KEY` | unknown_key verification status. | +| `UNVERIFIED` | unverified verification status. | +| `UNVERIFIED_KEY` | unverified_key verification status. | +| `VERIFIED` | verified verification status. | + ### `VisibilityLevelsEnum` | Value | Description | @@ -22903,6 +22981,23 @@ Implementations: | `value` | [`String`](#string) | Value of the variable. | | `variableType` | [`CiVariableType`](#civariabletype) | Type of the variable. | +#### `CommitSignature` + +Represents signing information for a commit. + +Implementations: + +- [`GpgSignature`](#gpgsignature) +- [`X509Signature`](#x509signature) + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `commitSha` | [`String`](#string) | SHA of the associated commit. | +| `project` | [`Project`](#project) | Project of the associated commit. | +| `verificationStatus` | [`VerificationStatus`](#verificationstatus) | Indicates verification status of the associated key or certificate. | + #### `CurrentUserTodos` Implementations: diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 7c446635096..306d8bddd80 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -379,7 +379,7 @@ start. Jobs in the current stage are not stopped and continue to run. - If a job does not specify a [`stage`](#stage), the job is assigned the `test` stage. - If a stage is defined but no jobs use it, the stage is not visible in the pipeline, - which can help [compliance pipeline configurations](../../user/group/manage.md#configure-a-compliance-pipeline): + which can help [compliance pipeline configurations](../../user/group/compliance_frameworks.md#configure-a-compliance-pipeline): - Stages can be defined in the compliance configuration but remain hidden if not used. - The defined stages become visible when developers use them in job definitions. diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 2f2778a9563..454f597f867 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -244,7 +244,7 @@ To enable or disable the banner: > [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/352316) from GitLab Premium to GitLab Ultimate in 15.0. NOTE: -An alternative [compliance solution](../../group/manage.md#configure-a-compliance-pipeline) +An alternative [compliance solution](../../group/compliance_frameworks.md#configure-a-compliance-pipeline) is available. We recommend this alternative solution because it provides greater flexibility, allowing required pipelines to be assigned to specific compliance framework labels. diff --git a/doc/user/application_security/get-started-security.md b/doc/user/application_security/get-started-security.md index b6213a98f91..5774bf940b0 100644 --- a/doc/user/application_security/get-started-security.md +++ b/doc/user/application_security/get-started-security.md @@ -36,7 +36,7 @@ The following steps will help you get the most from GitLab application security remediating existing vulnerabilities and preventing the introduction of new ones. 1. Enable other scan types such as [SAST](sast/index.md), [DAST](dast/index.md), [Fuzz testing](coverage_fuzzing/index.md), or [Container Scanning](container_scanning/index.md). -1. Use [Compliance Pipelines](../group/manage.md#configure-a-compliance-pipeline) +1. Use [Compliance Pipelines](../group/compliance_frameworks.md#configure-a-compliance-pipeline) or [Scan Execution Policies](policies/scan-execution-policies.md) to enforce required scan types and ensure separation of duties between security and engineering. 1. Consider enabling [Review Apps](../../development/testing_guide/review_apps.md) to allow for DAST diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 74791c5a7d3..30db267d891 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -487,7 +487,7 @@ Security and compliance teams must ensure that security scans: GitLab provides two methods of accomplishing this, each with advantages and disadvantages. -- [Compliance framework pipelines](../group/manage.md#configure-a-compliance-pipeline) +- [Compliance framework pipelines](../group/compliance_frameworks.md#configure-a-compliance-pipeline) are recommended when: - Scan execution enforcement is required for any scanner that uses a GitLab template, such as SAST IaC, DAST, Dependency Scanning, diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index 41d25dfa8c8..3bba3779ecf 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -15,7 +15,7 @@ with a long, random job name. In the unlikely event of a job name collision, the any pre-existing job in the pipeline. If a policy is created at the group-level, it will apply to every child project or subgroup. A group-level policy cannot be edited from a child project or subgroup. -This feature has some overlap with [compliance framework pipelines](../../group/manage.md#configure-a-compliance-pipeline), +This feature has some overlap with [compliance framework pipelines](../../group/compliance_frameworks.md#configure-a-compliance-pipeline), as we have not [unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). For details on the similarities and differences between these features, see [Enforce scan execution](../index.md#enforce-scan-execution). diff --git a/doc/user/group/compliance_frameworks.md b/doc/user/group/compliance_frameworks.md new file mode 100644 index 00000000000..7bd545003db --- /dev/null +++ b/doc/user/group/compliance_frameworks.md @@ -0,0 +1,262 @@ +--- +stage: Govern +group: Compliance +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 +--- + +# Compliance frameworks **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/287779) in GitLab 13.12. + +You can create a compliance framework that is a label to identify that your project has certain compliance +requirements or needs additional oversight. The label can optionally enforce +[compliance pipeline configuration](#configure-a-compliance-pipeline) to the projects on which it is +[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). + +Group owners can create, edit, and delete compliance frameworks: + +1. On the top bar, select **Main menu > Groups > View all groups** and find your group. +1. On the left sidebar, select **Settings** > **General**. +1. Expand the **Compliance frameworks** section. +1. Create, edit, or delete compliance frameworks. + +## Set a default compliance framework + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375036) in GitLab 15.6. + +Group owners can set a default compliance framework. The default framework is applied to all the new projects +that are created within that group. It does not affect the framework applied to the existing projects. The default +framework cannot be deleted. + +### Example GraphQL mutations for setting a default compliance framework + +Creating a new compliance framework and setting it as the default framework for the group. + +```graphql +mutation { + createComplianceFramework( + input: {params: {name: "SOX", description: "Sarbanes-Oxley Act", color: "#87CEEB", default: true}, namespacePath: "gitlab-org"} + ) { + framework { + id + name + default + description + color + pipelineConfigurationFullPath + } + errors + } +} +``` + +Setting an existing compliance framework as the default framework the group. + +```graphql +mutation { + updateComplianceFramework( + input: {id: "gid://gitlab/ComplianceManagement::Framework/", params: {default: true}} + ) { + complianceFramework { + id + name + default + description + color + pipelineConfigurationFullPath + } + } +} +``` + +## Configure a compliance pipeline **(ULTIMATE)** + +> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9, disabled behind `ff_evaluate_group_level_compliance_pipeline` [feature flag](../../administration/feature_flags.md). +> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/331231) in GitLab 14.2. + +Group owners can configure a compliance pipeline in a project separate to other projects. By default, the compliance +pipeline configuration (`.gitlab-ci.yml` file) is run instead of the pipeline configuration of labeled projects. + +However, the compliance pipeline configuration can reference the `.gitlab-ci.yml` file of the labeled projects so that: + +- The compliance pipeline can also run jobs of labeled project pipelines. This allows for centralized control of + pipeline configuration. +- Jobs and variables defined in the compliance pipeline can't be changed by variables in the labeled project's + `.gitlab-ci.yml` file. + +See [example configuration](#example-configuration) for help configuring a compliance pipeline that runs jobs from +labeled project pipeline configuration. + +To configure a compliance pipeline: + +1. On the top bar, select **Main menu > Groups > View all groups** and find your group. +1. On the left sidebar, select **Settings** > **General**. +1. Expand the **Compliance frameworks** section. +1. In **Compliance pipeline configuration (optional)**, add the path to the compliance framework configuration. Use the + `path/file.y[a]ml@group-name/project-name` format. For example: + + - `.compliance-ci.yml@gitlab-org/gitlab`. + - `.compliance-ci.yaml@gitlab-org/gitlab`. + +This configuration is inherited by projects where the compliance framework label is +[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). In projects with the applied compliance +framework label, the compliance pipeline configuration is run instead of the labeled project's own pipeline configuration. + +The user running the pipeline in the labeled project must at least have the Reporter role on the compliance project. + +When used to enforce scan execution, this feature has some overlap with +[scan execution policies](../application_security/policies/scan-execution-policies.md). We have not +[unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). For details on +the similarities and differences between these features, see [Enforce scan execution](../application_security/index.md#enforce-scan-execution). + +### Example configuration + +The following example `.compliance-gitlab-ci.yml` includes the `include` keyword to ensure labeled project pipeline +configuration is also executed. + +```yaml +# Allows compliance team to control the ordering and interweaving of stages/jobs. +# Stages without jobs defined will remain hidden. +stages: + - pre-compliance + - build + - test + - pre-deploy-compliance + - deploy + - post-compliance + +variables: # Can be overridden by setting a job-specific variable in project's local .gitlab-ci.yml + FOO: sast + +sast: # None of these attributes can be overridden by a project's local .gitlab-ci.yml + variables: + FOO: sast + image: ruby:2.6 + stage: pre-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +sanity check: + image: ruby:2.6 + stage: pre-deploy-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +audit trail: + image: ruby:2.7 + stage: post-compliance + rules: + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" + when: never + - when: always # or when: on_success + allow_failure: false + before_script: + - "# No before scripts." + script: + - echo "running $FOO" + after_script: + - "# No after scripts." + +include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) + project: '$CI_PROJECT_PATH' + file: '$CI_CONFIG_PATH' + ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch +``` + +#### CF pipelines in Merge Requests originating in project forks + +When an MR originates in a fork, the branch to be merged usually only exists in the fork. +When creating such an MR against a project with CF pipelines, the above snippet will fail with a +`Project reference does not exist!` error message. +This is because in the context of the target project, `$CI_COMMIT_REF_NAME` evaluates to a non-existing branch name. + +To get the correct context, use `$CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` instead of `$CI_PROJECT_PATH`. +This variable is only availabe in +[merge request pipelines](../../ci/pipelines/merge_request_pipelines.md). + +For example, for a configuration that supports both merge request pipelines originating in project forks and branch pipelines, +you need to [combine both `include` directives with `rules:if`](../../ci/yaml/includes.md#use-rules-with-include): + +```yaml +include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) + - project: '$CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' + file: '$CI_CONFIG_PATH' + ref: '$CI_COMMIT_REF_NAME' + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - project: '$CI_PROJECT_PATH' + file: '$CI_CONFIG_PATH' + ref: '$CI_COMMIT_REF_NAME' + rules: + - if: $CI_PIPELINE_SOURCE != 'merge_request_event' +``` + +## Ensure compliance jobs are always run + +Compliance pipelines [use GitLab CI/CD](../../ci/index.md) to give you an incredible amount of flexibility +for defining any sort of compliance jobs you like. Depending on your goals, these jobs +can be configured to be: + +- Modified by users. +- Non-modifiable. + +Generally, if a value in a compliance job: + +- Is set, it cannot be changed or overridden by project-level configurations. +- Is not set, a project-level configuration may set. + +Either might be wanted or not depending on your use case. + +There are a few best practices for ensuring that these jobs are always run exactly +as you define them and that downstream, project-level pipeline configurations +cannot change them: + +- Add [a `rules:when:always` block](../../ci/yaml/index.md#when) to each of your compliance jobs. This ensures they are + non-modifiable and are always run. +- Explicitly set any [variables](../../ci/yaml/index.md#variables) the job references. This: + - Ensures that project-level pipeline configurations do not set them and alter their + behavior. + - Includes any jobs that drive the logic of your job. +- Explicitly set the [container image](../../ci/yaml/index.md#image) to run the job in. This ensures that your script + steps execute in the correct environment. +- Explicitly set any relevant GitLab pre-defined [job keywords](../../ci/yaml/index.md#job-keywords). + This ensures that your job uses the settings you intend and that they are not overridden by + project-level pipelines. + +## Avoid parent and child pipelines in GitLab 14.7 and earlier + +NOTE: +This advice does not apply to GitLab 14.8 and later because [a fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78878) added +compatibility for combining compliance pipelines, and parent and child pipelines. + +Compliance pipelines start on the run of _every_ pipeline in a labeled project. This means that if a pipeline in the labeled project +triggers a child pipeline, the compliance pipeline runs first. This can trigger the parent pipeline, instead of the child pipeline. + +Therefore, in projects with compliance frameworks, we recommend replacing +[parent-child pipelines](../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines) with the following: + +- Direct [`include`](../../ci/yaml/index.md#include) statements that provide the parent pipeline with child pipeline configuration. +- Child pipelines placed in another project that are run using the [trigger API](../../ci/triggers/index.md) rather than the parent-child + pipeline feature. + +This alternative ensures the compliance pipeline does not re-start the parent pipeline. diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index ed2bab55f57..8ec9d6d44dd 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -397,263 +397,6 @@ To enable delayed deletion of projects in a group: NOTE: In GitLab 13.11 and above the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md) inheritance can be overridden, unless enforced by an ancestor. -## Compliance frameworks **(PREMIUM)** - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/287779) in GitLab 13.12. - -You can create a compliance framework that is a label to identify that your project has certain compliance -requirements or needs additional oversight. The label can optionally enforce -[compliance pipeline configuration](#configure-a-compliance-pipeline) to the projects on which it is -[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). - -Group owners can create, edit, and delete compliance frameworks: - -1. On the top bar, select **Main menu > Groups > View all groups** and find your group. -1. On the left sidebar, select **Settings** > **General**. -1. Expand the **Compliance frameworks** section. -1. Create, edit, or delete compliance frameworks. - -### Set a default compliance framework - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375036) in GitLab 15.6. - -Group owners can set a default compliance framework. The default framework is applied to all the new projects -that are created within that group. It does not affect the framework applied to the existing projects. The default -framework cannot be deleted. - -#### Example GraphQL mutations for setting a default compliance framework - -Creating a new compliance framework and setting it as the default framework for the group. - -```graphql -mutation { - createComplianceFramework( - input: {params: {name: "SOX", description: "Sarbanes-Oxley Act", color: "#87CEEB", default: true}, namespacePath: "gitlab-org"} - ) { - framework { - id - name - default - description - color - pipelineConfigurationFullPath - } - errors - } -} -``` - -Setting an existing compliance framework as the default framework the group. - -```graphql -mutation { - updateComplianceFramework( - input: {id: "gid://gitlab/ComplianceManagement::Framework/", params: {default: true}} - ) { - complianceFramework { - id - name - default - description - color - pipelineConfigurationFullPath - } - } -} -``` - -### Configure a compliance pipeline **(ULTIMATE)** - -> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9, disabled behind `ff_evaluate_group_level_compliance_pipeline` [feature flag](../../administration/feature_flags.md). -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/331231) in GitLab 14.2. - -Group owners can configure a compliance pipeline in a project separate to other projects. By default, the compliance -pipeline configuration (`.gitlab-ci.yml` file) is run instead of the pipeline configuration of labeled projects. - -However, the compliance pipeline configuration can reference the `.gitlab-ci.yml` file of the labeled projects so that: - -- The compliance pipeline can also run jobs of labeled project pipelines. This allows for centralized control of - pipeline configuration. -- Jobs and variables defined in the compliance pipeline can't be changed by variables in the labeled project's - `.gitlab-ci.yml` file. - -See [example configuration](#example-configuration) for help configuring a compliance pipeline that runs jobs from -labeled project pipeline configuration. - -To configure a compliance pipeline: - -1. On the top bar, select **Main menu > Groups > View all groups** and find your group. -1. On the left sidebar, select **Settings** > **General**. -1. Expand the **Compliance frameworks** section. -1. In **Compliance pipeline configuration (optional)**, add the path to the compliance framework configuration. Use the - `path/file.y[a]ml@group-name/project-name` format. For example: - - - `.compliance-ci.yml@gitlab-org/gitlab`. - - `.compliance-ci.yaml@gitlab-org/gitlab`. - -This configuration is inherited by projects where the compliance framework label is -[applied](../project/settings/index.md#add-a-compliance-framework-to-a-project). In projects with the applied compliance -framework label, the compliance pipeline configuration is run instead of the labeled project's own pipeline configuration. - -The user running the pipeline in the labeled project must at least have the Reporter role on the compliance project. - -When used to enforce scan execution, this feature has some overlap with -[scan execution policies](../application_security/policies/scan-execution-policies.md). We have not -[unified the user experience for these two features](https://gitlab.com/groups/gitlab-org/-/epics/7312). For details on -the similarities and differences between these features, see [Enforce scan execution](../application_security/index.md#enforce-scan-execution). - -#### Example configuration - -The following example `.compliance-gitlab-ci.yml` includes the `include` keyword to ensure labeled project pipeline -configuration is also executed. - -```yaml -# Allows compliance team to control the ordering and interweaving of stages/jobs. -# Stages without jobs defined will remain hidden. -stages: - - pre-compliance - - build - - test - - pre-deploy-compliance - - deploy - - post-compliance - -variables: # Can be overridden by setting a job-specific variable in project's local .gitlab-ci.yml - FOO: sast - -sast: # None of these attributes can be overridden by a project's local .gitlab-ci.yml - variables: - FOO: sast - image: ruby:2.6 - stage: pre-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -sanity check: - image: ruby:2.6 - stage: pre-deploy-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -audit trail: - image: ruby:2.7 - stage: post-compliance - rules: - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" - when: never - - when: always # or when: on_success - allow_failure: false - before_script: - - "# No before scripts." - script: - - echo "running $FOO" - after_script: - - "# No after scripts." - -include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) - project: '$CI_PROJECT_PATH' - file: '$CI_CONFIG_PATH' - ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch -``` - -##### CF pipelines in Merge Requests originating in project forks - -When an MR originates in a fork, the branch to be merged usually only exists in the fork. -When creating such an MR against a project with CF pipelines, the above snippet will fail with a -`Project reference does not exist!` error message. -This is because in the context of the target project, `$CI_COMMIT_REF_NAME` evaluates to a non-existing branch name. - -To get the correct context, use `$CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` instead of `$CI_PROJECT_PATH`. -This variable is only availabe in -[merge request pipelines](../../ci/pipelines/merge_request_pipelines.md). - -For example, for a configuration that supports both merge request pipelines originating in project forks and branch pipelines, -you need to [combine both `include` directives with `rules:if`](../../ci/yaml/includes.md#use-rules-with-include): - -```yaml -include: # Execute individual project's configuration (if project contains .gitlab-ci.yml) - - project: '$CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' - file: '$CI_CONFIG_PATH' - ref: '$CI_COMMIT_REF_NAME' - rules: - - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - - project: '$CI_PROJECT_PATH' - file: '$CI_CONFIG_PATH' - ref: '$CI_COMMIT_REF_NAME' - rules: - - if: $CI_PIPELINE_SOURCE != 'merge_request_event' -``` - -### Ensure compliance jobs are always run - -Compliance pipelines [use GitLab CI/CD](../../ci/index.md) to give you an incredible amount of flexibility -for defining any sort of compliance jobs you like. Depending on your goals, these jobs -can be configured to be: - -- Modified by users. -- Non-modifiable. - -Generally, if a value in a compliance job: - -- Is set, it cannot be changed or overridden by project-level configurations. -- Is not set, a project-level configuration may set. - -Either might be wanted or not depending on your use case. - -There are a few best practices for ensuring that these jobs are always run exactly -as you define them and that downstream, project-level pipeline configurations -cannot change them: - -- Add [a `rules:when:always` block](../../ci/yaml/index.md#when) to each of your compliance jobs. This ensures they are - non-modifiable and are always run. -- Explicitly set any [variables](../../ci/yaml/index.md#variables) the job references. This: - - Ensures that project-level pipeline configurations do not set them and alter their - behavior. - - Includes any jobs that drive the logic of your job. -- Explicitly set the [container image](../../ci/yaml/index.md#image) to run the job in. This ensures that your script - steps execute in the correct environment. -- Explicitly set any relevant GitLab pre-defined [job keywords](../../ci/yaml/index.md#job-keywords). - This ensures that your job uses the settings you intend and that they are not overridden by - project-level pipelines. - -### Avoid parent and child pipelines in GitLab 14.7 and earlier - -NOTE: -This advice does not apply to GitLab 14.8 and later because [a fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78878) added -compatibility for combining compliance pipelines, and parent and child pipelines. - -Compliance pipelines start on the run of _every_ pipeline in a labeled project. This means that if a pipeline in the labeled project -triggers a child pipeline, the compliance pipeline runs first. This can trigger the parent pipeline, instead of the child pipeline. - -Therefore, in projects with compliance frameworks, we recommend replacing -[parent-child pipelines](../../ci/pipelines/downstream_pipelines.md#parent-child-pipelines) with the following: - -- Direct [`include`](../../ci/yaml/index.md#include) statements that provide the parent pipeline with child pipeline configuration. -- Child pipelines placed in another project that are run using the [trigger API](../../ci/triggers/index.md) rather than the parent-child - pipeline feature. - -This alternative ensures the compliance pipeline does not re-start the parent pipeline. - ## Disable email notifications > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23585) in GitLab 12.2. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index eb26ea4fcc5..7eb2c649f84 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -45,7 +45,7 @@ If you're an instance administrator, you can administer all project topics from ## Add a compliance framework to a project **(PREMIUM)** -[Compliance frameworks](../../group/manage.md#compliance-frameworks) can be assigned to projects within group that has a +[Compliance frameworks](../../group/compliance_frameworks.md) can be assigned to projects within group that has a compliance framework using either: - The GitLab UI: diff --git a/lib/api/api.rb b/lib/api/api.rb index 9bef9d826b3..fb71b8b048d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -188,6 +188,7 @@ module API mount ::API::ProjectRepositoryStorageMoves mount ::API::Release::Links mount ::API::ResourceAccessTokens + mount ::API::ProtectedTags mount ::API::SnippetRepositoryStorageMoves mount ::API::ProtectedBranches mount ::API::Statistics diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 37c7cc73c46..b3a0a9ef54a 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -38,7 +38,7 @@ module API latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) authorize_read_job_artifacts!(latest_build) - present_artifacts_file!(latest_build.artifacts_file, project: latest_build.project) + present_artifacts_file!(latest_build.artifacts_file) end desc 'Download a specific file from artifacts archive from a ref' do @@ -80,7 +80,7 @@ module API build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - present_artifacts_file!(build.artifacts_file, project: build.project) + present_artifacts_file!(build.artifacts_file) end desc 'Download a specific file from artifacts archive' do diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index a75741ac695..6497b8cadaf 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -349,7 +349,7 @@ module API authenticate_job!(require_running: false) end - present_artifacts_file!(current_job.artifacts_file, project: current_job.project, supports_direct_download: params[:direct_download]) + present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) end end end diff --git a/lib/api/entities/protected_tag.rb b/lib/api/entities/protected_tag.rb index dc397f01af6..ba984ae79b8 100644 --- a/lib/api/entities/protected_tag.rb +++ b/lib/api/entities/protected_tag.rb @@ -3,7 +3,7 @@ module API module Entities class ProtectedTag < Grape::Entity - expose :name + expose :name, documentation: { type: 'string', example: 'release-1-0' } expose :create_access_levels, using: Entities::ProtectedRefAccess end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 99f759b50d2..75e7612bd5b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -592,19 +592,19 @@ module API end end - def present_artifacts_file!(file, project:, **args) + def present_artifacts_file!(file, **args) log_artifacts_filesize(file&.model) - present_carrierwave_file!(file, project: project, **args) + present_carrierwave_file!(file, **args) end - def present_carrierwave_file!(file, project: nil, supports_direct_download: true) + def present_carrierwave_file!(file, supports_direct_download: true) return not_found! unless file&.exists? if file.file_storage? present_disk_file!(file.path, file.filename) elsif supports_direct_download && file.class.direct_download_enabled? - redirect(cdn_fronted_url(file, project)) + redirect(cdn_fronted_url(file)) else header(*Gitlab::Workhorse.send_url(file.url)) status :ok @@ -612,9 +612,9 @@ module API end end - def cdn_fronted_url(file, project) + def cdn_fronted_url(file) if file.respond_to?(:cdn_enabled_url) - result = file.cdn_enabled_url(project, ip_address) + result = file.cdn_enabled_url(ip_address) Gitlab::ApplicationContext.push(artifact_used_cdn: result.used_cdn) result.url else diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb index 4611ee58479..7d4d15ab4b3 100644 --- a/lib/api/protected_tags.rb +++ b/lib/api/protected_tags.rb @@ -18,7 +18,13 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "Get a project's protected tags" do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + is_array true + success code: 200, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[protected_tags] end params do use :pagination @@ -33,10 +39,15 @@ module API desc 'Get a single protected tag' do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + success code: 200, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the tag or wildcard' + requires :name, type: String, desc: 'The name of the tag or wildcard', documentation: { example: 'release*' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do @@ -48,13 +59,21 @@ module API desc 'Protect a single tag or wildcard' do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + success code: 201, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the protected tag' - optional :create_access_level, type: Integer, - values: ProtectedTag::CreateAccessLevel.allowed_access_levels, - desc: 'Access levels allowed to create (defaults: `40`, maintainer access level)' + requires :name, type: String, desc: 'The name of the protected tag', documentation: { example: 'release-1-0' } + optional :create_access_level, + type: Integer, + values: ProtectedTag::CreateAccessLevel.allowed_access_levels, + desc: 'Access levels allowed to create (defaults: `40`, maintainer access level)', + documentation: { example: 30 } use :optional_params_ee end post ':id/protected_tags' do @@ -76,9 +95,16 @@ module API desc 'Unprotect a single tag' do detail 'This feature was introduced in GitLab 11.3.' + success code: 204 + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the protected tag' + requires :name, type: String, desc: 'The name of the protected tag', documentation: { example: 'release-1-0' } end # rubocop: disable CodeReuse/ActiveRecord delete ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb index a27f9a7a347..0fa38521657 100644 --- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb +++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb @@ -352,7 +352,7 @@ module Gitlab end def cookie_key - "#{idempotency_key}:cookie" + "#{idempotency_key}:cookie:v2" end def get_cookie diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 767d58b8f92..23cfdb2359d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -31814,6 +31814,9 @@ msgstr "" msgid "ProjectSettings|Combine git tags with release notes, release evidence, and assets to create a release." msgstr "" +msgid "ProjectSettings|Configure your infrastructure." +msgstr "" + msgid "ProjectSettings|Configure your project resources and monitor their health." msgstr "" @@ -31913,6 +31916,9 @@ msgstr "" msgid "ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts." msgstr "" +msgid "ProjectSettings|Infrastructure" +msgstr "" + msgid "ProjectSettings|Internal" msgstr "" diff --git a/spec/features/projects/user_changes_project_visibility_spec.rb b/spec/features/projects/user_changes_project_visibility_spec.rb index d2a7596aec0..df13bb55c6d 100644 --- a/spec/features/projects/user_changes_project_visibility_spec.rb +++ b/spec/features/projects/user_changes_project_visibility_spec.rb @@ -103,6 +103,9 @@ RSpec.describe 'User changes public project visibility', :js do sign_in(project.first_owner) visit edit_project_path(project) + + # https://gitlab.com/gitlab-org/gitlab/-/issues/381259 + allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(110) end it_behaves_like 'does not require confirmation' diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap index aab78c99190..6b6833b00c3 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap @@ -147,6 +147,7 @@ exports[`Learn GitLab renders correctly 1`] = `
{ const findConfirmDangerButton = () => wrapper.findComponent(ConfirmDanger); const findEnvironmentsSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); const findFeatureFlagsSettings = () => wrapper.findComponent({ ref: 'feature-flags-settings' }); + const findInfrastructureSettings = () => + wrapper.findComponent({ ref: 'infrastructure-settings' }); const findReleasesSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' }); @@ -841,6 +843,24 @@ describe('Settings Panel', () => { }); }); }); + describe('Infrastructure', () => { + describe('with feature flag', () => { + it('should show the infrastructure toggle', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + }); + + expect(findInfrastructureSettings().exists()).toBe(true); + }); + }); + describe('without feature flag', () => { + it('should not show the infrastructure toggle', () => { + wrapper = mountComponent({}); + + expect(findInfrastructureSettings().exists()).toBe(false); + }); + }); + }); describe('Releases', () => { describe('with feature flag', () => { it('should show the releases toggle', () => { diff --git a/spec/graphql/types/commit_signature_interface_spec.rb b/spec/graphql/types/commit_signature_interface_spec.rb new file mode 100644 index 00000000000..4962131d9b5 --- /dev/null +++ b/spec/graphql/types/commit_signature_interface_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CommitSignature'] do + it 'exposes the expected fields' do + expect(described_class).to have_graphql_fields(:verification_status, :commit_sha, :project) + end + + describe '.resolve_type' do + it 'resolves gpg signatures' do + expect(described_class.resolve_type(build(:gpg_signature), {})).to eq( + Types::CommitSignatures::GpgSignatureType) + end + + it 'resolves x509 signatures' do + expect(described_class.resolve_type(build(:x509_commit_signature), {})).to eq( + Types::CommitSignatures::X509SignatureType) + end + + it 'raises an error when type is not known' do + expect { described_class.resolve_type(Class, {}) }.to raise_error('Unsupported commit signature type') + end + end +end diff --git a/spec/graphql/types/commit_signatures/gpg_signature_type_spec.rb b/spec/graphql/types/commit_signatures/gpg_signature_type_spec.rb new file mode 100644 index 00000000000..0b69ee169f2 --- /dev/null +++ b/spec/graphql/types/commit_signatures/gpg_signature_type_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['GpgSignature'] do + specify { expect(described_class.graphql_name).to eq('GpgSignature') } + + specify { expect(described_class).to require_graphql_authorizations(:download_code) } + + specify { expect(described_class).to include(Types::CommitSignatureInterface) } + + it 'contains attributes related to GPG signatures' do + expect(described_class).to have_graphql_fields( + :user, :verification_status, :commit_sha, :project, + :gpg_key_user_name, :gpg_key_user_email, :gpg_key_primary_keyid + ) + end +end diff --git a/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb b/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb new file mode 100644 index 00000000000..cb7ce19c9fc --- /dev/null +++ b/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['VerificationStatus'] do + specify { expect(described_class.graphql_name).to eq('VerificationStatus') } + + it 'exposes all signature verification states' do + expect(described_class.values.keys) + .to match_array(%w[ + UNVERIFIED UNVERIFIED_KEY VERIFIED + SAME_USER_DIFFERENT_EMAIL OTHER_USER UNKNOWN_KEY + MULTIPLE_SIGNATURES + ]) + end +end diff --git a/spec/graphql/types/commit_signatures/x509_signature_type_spec.rb b/spec/graphql/types/commit_signatures/x509_signature_type_spec.rb new file mode 100644 index 00000000000..e268bd5b3b4 --- /dev/null +++ b/spec/graphql/types/commit_signatures/x509_signature_type_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['X509Signature'] do + specify { expect(described_class.graphql_name).to eq('X509Signature') } + + specify { expect(described_class).to require_graphql_authorizations(:download_code) } + + specify { expect(described_class).to include(Types::CommitSignatureInterface) } + + it 'contains attributes related to X.509 signatures' do + expect(described_class).to have_graphql_fields( + :user, :verification_status, :commit_sha, :project, + :x509_certificate + ) + end +end diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb index fe8df15028d..8c1cfab081b 100644 --- a/spec/graphql/types/commit_type_spec.rb +++ b/spec/graphql/types/commit_type_spec.rb @@ -13,7 +13,7 @@ RSpec.describe GitlabSchema.types['Commit'] do expect(described_class).to have_graphql_fields( :id, :sha, :short_id, :title, :full_title, :full_title_html, :description, :description_html, :message, :title_html, :authored_date, :author_name, :author_email, :author_gravatar, :author, :web_url, :web_path, - :pipelines, :signature_html + :pipelines, :signature_html, :signature ) end end diff --git a/spec/graphql/types/x509_certificate_type_spec.rb b/spec/graphql/types/x509_certificate_type_spec.rb new file mode 100644 index 00000000000..e59d1f83b28 --- /dev/null +++ b/spec/graphql/types/x509_certificate_type_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['X509Certificate'] do + specify { expect(described_class.graphql_name).to eq('X509Certificate') } + + it 'contains attributes for X.509 certifcates' do + expect(described_class).to have_graphql_fields( + :certificate_status, :created_at, :email, :id, :serial_number, :subject, + :subject_key_identifier, :updated_at, :x509_issuer + ) + end +end diff --git a/spec/graphql/types/x509_issuer_type_spec.rb b/spec/graphql/types/x509_issuer_type_spec.rb new file mode 100644 index 00000000000..5446dcf07c7 --- /dev/null +++ b/spec/graphql/types/x509_issuer_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['X509Issuer'] do + specify { expect(described_class.graphql_name).to eq('X509Issuer') } + + it 'contains attributes for X.509 issuers' do + expect(described_class).to have_graphql_fields( + :created_at, :crl_url, :id, :subject, :subject_key_identifier, :updated_at + ) + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 07c2d50f70a..39b8b552672 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -969,7 +969,8 @@ RSpec.describe ProjectsHelper do containerRegistryAccessLevel: project.project_feature.container_registry_access_level, environmentsAccessLevel: project.project_feature.environments_access_level, featureFlagsAccessLevel: project.project_feature.feature_flags_access_level, - releasesAccessLevel: project.project_feature.releases_access_level + releasesAccessLevel: project.project_feature.releases_access_level, + infrastructureAccessLevel: project.project_feature.infrastructure_access_level ) end diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 652727f371b..d24a3bd13c0 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -798,7 +798,7 @@ RSpec.describe API::Helpers do context 'with object storage' do let(:artifact) { create(:ci_job_artifact, :zip, :remote_store) } - subject { helper.present_artifacts_file!(artifact.file, project: artifact.job.project) } + subject { helper.present_artifacts_file!(artifact.file) } before do allow(helper).to receive(:env).and_return({}) @@ -830,7 +830,7 @@ RSpec.describe API::Helpers do it 'retrieves a CDN-fronted URL' do expect(artifact.file).to receive(:cdn_enabled_url).and_call_original expect(Gitlab::ApplicationContext).to receive(:push).with(artifact_used_cdn: false).and_call_original - expect(helper.cdn_fronted_url(artifact.file, artifact.job.project)).to be_a(String) + expect(helper.cdn_fronted_url(artifact.file)).to be_a(String) end end @@ -841,7 +841,7 @@ RSpec.describe API::Helpers do file = double(url: url) expect(Gitlab::ApplicationContext).not_to receive(:push) - expect(helper.cdn_fronted_url(file, nil)).to eq(url) + expect(helper.cdn_fronted_url(file)).to eq(url) end end end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb index 1e8cb76905b..ead7265ee42 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -527,7 +527,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi end context 'with Redis cookies' do - let(:cookie_key) { "#{idempotency_key}:cookie" } + let(:cookie_key) { "#{idempotency_key}:cookie:v2" } let(:cookie) { get_redis_msgpack(cookie_key) } def with_redis(&block) diff --git a/spec/models/commit_signatures/gpg_signature_spec.rb b/spec/models/commit_signatures/gpg_signature_spec.rb index 605ad725dd7..1ffaaeba396 100644 --- a/spec/models/commit_signatures/gpg_signature_spec.rb +++ b/spec/models/commit_signatures/gpg_signature_spec.rb @@ -85,4 +85,10 @@ RSpec.describe CommitSignatures::GpgSignature do end end end + + describe '#user' do + it 'retrieves the gpg_key user' do + expect(signature.user).to eq(gpg_key.user) + end + end end diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb index 2bf242f06ed..da9eb6b2216 100644 --- a/spec/requests/api/ci/job_artifacts_spec.rb +++ b/spec/requests/api/ci/job_artifacts_spec.rb @@ -389,8 +389,7 @@ RSpec.describe API::Ci::JobArtifacts do end end - context 'when Google CDN is enabled' do - let(:cdn_enabled) { true } + context 'when Google CDN is configured' do let(:cdn_config) do { 'provider' => 'Google', @@ -401,7 +400,6 @@ RSpec.describe API::Ci::JobArtifacts do end before do - stub_feature_flags(ci_job_artifacts_cdn: cdn_enabled) stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store, uploader: JobArtifactUploader, proxy_download: proxy_download, @@ -418,18 +416,6 @@ RSpec.describe API::Ci::JobArtifacts do expect(response.redirect_url).to start_with("https://cdn.example.org/#{artifact.file.path}") end - - context 'when ci_job_artifacts_cdn feature flag is disabled' do - let(:cdn_enabled) { false } - - it 'returns the file remote URL' do - expect(Gitlab::ApplicationContext).to receive(:push).with(artifact_used_cdn: false).and_call_original - - subject - - expect(response).to redirect_to(artifact.file.url) - end - end end context 'authorized user' do diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb index 25e878a5b1a..e63e0d3dd04 100644 --- a/spec/requests/api/graphql/project/tree/tree_spec.rb +++ b/spec/requests/api/graphql/project/tree/tree_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' RSpec.describe 'getting a tree in a project' do include GraphqlHelpers - let(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository) } + let(:current_user) { project.first_owner } let(:path) { "" } let(:ref) { "master" } @@ -82,6 +83,89 @@ RSpec.describe 'getting a tree in a project' do end end + context 'when the ref points to a gpg-signed commit with a user' do + let_it_be(:name) { GpgHelpers::User1.names.first } + let_it_be(:email) { GpgHelpers::User1.emails.first } + let_it_be(:current_user) { create(:user, name: name, email: email).tap { |user| project.add_owner(user) } } + let_it_be(:gpg_key) { create(:gpg_key, user: current_user, key: GpgHelpers::User1.public_key) } + + let(:ref) { GpgHelpers::SIGNED_AND_AUTHORED_SHA } + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + lastCommit { + signature { + ... on GpgSignature { + #{all_graphql_fields_for('GpgSignature'.classify, max_depth: 2)} + } + } + } + } + QUERY + end + + before do + post_graphql(query, current_user: current_user) + end + + it 'returns the expected signature data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + expect(signature['commitSha']).to eq(ref) + expect(signature['user']['id']).to eq("gid://gitlab/User/#{current_user.id}") + expect(signature['gpgKeyUserName']).to eq(name) + expect(signature['gpgKeyUserEmail']).to eq(email) + expect(signature['verificationStatus']).to eq('VERIFIED') + expect(signature['project']['id']).to eq("gid://gitlab/Project/#{project.id}") + end + end + + context 'when the ref points to a X.509-signed commit' do + let_it_be(:email) { X509Helpers::User1.certificate_email } + let_it_be(:current_user) { create(:user, email: email).tap { |user| project.add_owner(user) } } + + let(:ref) { X509Helpers::User1.commit } + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + lastCommit { + signature { + ... on X509Signature { + #{all_graphql_fields_for('X509Signature'.classify, max_depth: 2)} + } + } + } + } + QUERY + end + + before do + store = OpenSSL::X509::Store.new + store.add_cert(OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert)) + allow(OpenSSL::X509::Store).to receive(:new).and_return(store) + post_graphql(query, current_user: current_user) + end + + it 'returns the expected signature data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + expect(signature['commitSha']).to eq(ref) + expect(signature['verificationStatus']).to eq('VERIFIED') + expect(signature['project']['id']).to eq("gid://gitlab/Project/#{project.id}") + end + + it 'returns expected certificate data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + certificate = signature['x509Certificate'] + expect(certificate['certificateStatus']).to eq('good') + expect(certificate['email']).to eq(X509Helpers::User1.certificate_email) + expect(certificate['id']).to be_present + expect(certificate['serialNumber']).to eq(X509Helpers::User1.certificate_serial.to_s) + expect(certificate['subject']).to eq(X509Helpers::User1.certificate_subject) + expect(certificate['subjectKeyIdentifier']).to eq(X509Helpers::User1.certificate_subject_key_identifier) + expect(certificate['createdAt']).to be_present + expect(certificate['updatedAt']).to be_present + end + end + context 'when current user is nil' do it 'returns empty project' do post_graphql(query, current_user: nil) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index f6e90d8b3cc..31d24052b3e 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -11,6 +11,8 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { 'signed-commits' => 'c7794c1', + 'gpg-signed' => '8a852d5', + 'x509-signed' => 'a4df3c8', 'not-merged-branch' => 'b83d6e3', 'branch-merged' => '498214d', 'empty-branch' => '7efb185', diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb index a7fd040837f..d7c9ef7e0d5 100644 --- a/spec/uploaders/job_artifact_uploader_spec.rb +++ b/spec/uploaders/job_artifact_uploader_spec.rb @@ -26,7 +26,7 @@ RSpec.describe JobArtifactUploader do describe '#cdn_enabled_url' do it 'returns URL and false' do - result = uploader.cdn_enabled_url(nil, '127.0.0.1') + result = uploader.cdn_enabled_url('127.0.0.1') expect(result.used_cdn).to be false end diff --git a/spec/uploaders/object_storage/cdn_spec.rb b/spec/uploaders/object_storage/cdn_spec.rb index f99450b274f..2a447921a19 100644 --- a/spec/uploaders/object_storage/cdn_spec.rb +++ b/spec/uploaders/object_storage/cdn_spec.rb @@ -44,30 +44,13 @@ RSpec.describe ObjectStorage::CDN do end describe '#cdn_enabled_url' do - context 'with ci_job_artifacts_cdn feature flag disabled' do - before do - stub_feature_flags(ci_job_artifacts_cdn: false) - end + it 'calls #cdn_signed_url' do + expect(subject).not_to receive(:url) + expect(subject).to receive(:cdn_signed_url).and_call_original - it 'calls #url' do - expect(subject).to receive(:url).and_call_original - expect(subject).not_to receive(:cdn_signed_url) + result = subject.cdn_enabled_url(public_ip) - result = subject.cdn_enabled_url(project, public_ip) - - expect(result.used_cdn).to be false - end - end - - context 'with ci_job_artifacts_cdn feature flag enabled' do - it 'calls #cdn_signed_url' do - expect(subject).not_to receive(:url) - expect(subject).to receive(:cdn_signed_url).and_call_original - - result = subject.cdn_enabled_url(project, public_ip) - - expect(result.used_cdn).to be true - end + expect(result.used_cdn).to be true end end