diff --git a/app/models/ci/slsa/provenance_statement.rb b/app/models/ci/slsa/provenance_statement.rb index bfbb1f8718950e07dde78973599cd9638cb4ddf3..3b97180e1518598e0000e878bae6ee8abc6af13e 100644 --- a/app/models/ci/slsa/provenance_statement.rb +++ b/app/models/ci/slsa/provenance_statement.rb @@ -58,8 +58,7 @@ class BuildDefinition attr_accessor :build_type, :external_parameters, :internal_parameters, :resolved_dependencies def self.from_build(build) - # TODO: update buildType as part of https://gitlab.com/gitlab-org/gitlab/-/issues/426764 - build_type = "https://gitlab.com/gitlab-org/gitlab/-/issues/546150" + build_type = "https://docs.gitlab.com/ci/pipeline_security/slsa/provenance_v1" external_parameters = ExternalParameters.from_build(build) internal_parameters = { architecture: build.runner_manager.architecture, diff --git a/doc/ci/pipeline_security/_index.md b/doc/ci/pipeline_security/_index.md index 6878da93857d2ba6848186836e8615ddebd6646b..200bfa04f76fce5c13ef602e84604e3ef30d41a0 100644 --- a/doc/ci/pipeline_security/_index.md +++ b/doc/ci/pipeline_security/_index.md @@ -201,23 +201,6 @@ include: - local: '/ci/security-scan.yml' # Verified and stored in the repository ``` -### SLSA provenance generation - -GitLab offers a SLSA Level 1 compliant provenance statement that can be -[automatically generated for all build artifacts produced by the GitLab Runner](../runners/configure_runners.md#artifact-provenance-metadata). -This provenance statement is produced by the runner itself. - -#### Sign and verify SLSA provenance with a CI/CD Component - -The [GitLab SLSA CI/CD component](https://gitlab.com/explore/catalog/components/slsa) -provides configurations for: - -- Signing runner-generated provenance statements. -- Generating [Verification Summary Attestations (VSA)](https://slsa.dev/spec/v1.0/verification_summary) - for job artifacts. - -For more information and example configurations, see the [SLSA Component documentation](https://gitlab.com/components/slsa#slsa-supply-chain-levels-for-software-artifacts). - ### Related topics 1. [CIS Docker Benchmarks](https://www.cisecurity.org/benchmark/docker) diff --git a/doc/ci/pipeline_security/slsa/_index.md b/doc/ci/pipeline_security/slsa/_index.md new file mode 100644 index 0000000000000000000000000000000000000000..0985abbed245e844413590c542452fb9906746af --- /dev/null +++ b/doc/ci/pipeline_security/slsa/_index.md @@ -0,0 +1,29 @@ +--- +stage: Software Supply Chain Security +group: Pipeline Security +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +title: GitLab SLSA +--- + +This page contains information pertaining to GitLab SLSA support. + +Related topics: + +- [Provenance version 1 buildType specification](provenance_v1.md) + +### SLSA provenance generation + +GitLab offers a SLSA Level 1 compliant provenance statement that can be +[automatically generated for all build artifacts produced by the GitLab Runner](../../runners/configure_runners.md#artifact-provenance-metadata). +This provenance statement is produced by the runner itself. + +#### Sign and verify SLSA provenance with a CI/CD Component + +The [GitLab SLSA CI/CD component](https://gitlab.com/explore/catalog/components/slsa) +provides configurations for: + +- Signing runner-generated provenance statements. +- Generating [Verification Summary Attestations (VSA)](https://slsa.dev/spec/v1.0/verification_summary) + for job artifacts. + +For more information and example configurations, see the [SLSA Component documentation](https://gitlab.com/components/slsa#slsa-supply-chain-levels-for-software-artifacts). diff --git a/doc/ci/pipeline_security/slsa/provenance_v1.md b/doc/ci/pipeline_security/slsa/provenance_v1.md new file mode 100644 index 0000000000000000000000000000000000000000..ae9da0fdf5056d3ec8cf3c39d00cfb6283266a4e --- /dev/null +++ b/doc/ci/pipeline_security/slsa/provenance_v1.md @@ -0,0 +1,139 @@ +--- +stage: Software Supply Chain Security +group: Pipeline Security +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +title: SLSA provenance specification +--- + +{{< details >}} + +- Tier: Free, Premium, Ultimate +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated +- Status: Experiment + +{{< /details >}} + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/547865) in GitLab 18.3 [with a flag](../../../administration/feature_flags/_index.md) named `slsa_provenance_statement`. Disabled by default. + +{{< /history >}} + +{{< alert type="flag" >}} + +The availability of this feature is controlled by a feature flag. +For more information, see the history. +This feature is available for testing, but not ready for production use. + +{{< /alert >}} + +The [SLSA provenance specification](https://slsa.dev/spec/v1.1/provenance) requires +the `buildType` reference to be documented and published. This reference is to assist consumers of +GitLab SLSA attestations with parsing specific fields that are unique to GitLab SLSA provenance statements. + +See the SLSA [`buildType` documentation](https://slsa.dev/spec/v1.1/provenance#builddefinition) +for more details. + +## `buildType` + +This official [SLSA Provenance](https://slsa.dev/spec/v1.1/provenance) `buildType` reference: + +- Describes the execution of a GitLab [CI/CD job](_index.md). +- Is hosted and maintained by GitLab. + +### Description + +This `buildType` describes the execution of a workflow that builds a software +artifact. + +{{< alert type="note" >}} + +Consumers should ignore unrecognized external parameters. Any changes must +not change the semantics of existing external parameters. + +{{< /alert >}} + +### External parameters + +The external parameters: + +| Field | Value | +|--------------|-------| +| `source` | The URL of the project. | +| `entryPoint` | The name of the CI/CD job that triggered the build. | +| `variables` | The names and values of any CI/CD or environment variables available during the build command execution. If the variable is [masked or hidden](../../variables/_index.md) the value of the variable is set to `[MASKED]`. | + +### Internal parameters + +The internal parameters, which are populated by default: + +| Field | Value | +|----------------|-------| +| `name` | The name of the runner. | +| `executor` | The runner executor. | +| `architecture` | The architecture on which the CI/CD job is run. | +| `job` | The ID of the CI/CD job that triggered the build. | + +### Example + +This example shows the format of a GitLab-generated provenance statement: + +```json +{ + "_type": "https://in-toto.io/Statement/v1", + "subject": [ + { + "name": "artifacts.zip", + "digest": { + "sha256": "717a1ee89f0a2829cf5aad57054c83615675b04baa913bdc19999d7519edf3f2" + } + } + ], + "predicateType": "https://slsa.dev/provenance/v1", + "predicate": { + "buildDefinition": { + "buildType": "", + "externalParameters": { + "source": "http://gdk.test:3000/root/repo_name", + "entryPoint": "build-job", + "variables": { + "CI_PIPELINE_ID": "576", + "CI_PIPELINE_URL": "http://gdk.test:3000/root/repo_name/-/pipelines/576", + "CI_JOB_ID": "412", +[... additional environment variables ...] + "masked_and_hidden_variable": "[MASKED]", + "masked_variable": "[MASKED]", + "visible_variable": "visible_variable", + } + }, + "internalParameters": { + "architecture": "arm64", + "executor": "docker", + "job": 412, + "name": "9-mfdkBG" + }, + "resolvedDependencies": [ + { + "uri": "http://gdk.test:3000/root/repo_name", + "digest": { + "gitCommit": "a288201509dd9a85da4141e07522bad412938dbe" + } + } + ] + }, + "runDetails": { + "builder": { + "id": "http://gdk.test:3000/groups/user/-/runners/33", + "version": { + "gitlab-runner": "4d7093e1" + } + }, + "metadata": { + "invocationId": 412, + "startedOn": "2025-06-05T01:33:18Z", + "finishedOn": "2025-06-05T01:33:23Z" + } + } + } +} +``` diff --git a/ee/app/workers/ee/ci/build_finished_worker.rb b/ee/app/workers/ee/ci/build_finished_worker.rb index 97b71e1a0021e5ad21fb0790250192f8dc1903d3..189d200322cc78a0dab093465868b8a570f6cace 100644 --- a/ee/app/workers/ee/ci/build_finished_worker.rb +++ b/ee/app/workers/ee/ci/build_finished_worker.rb @@ -21,6 +21,8 @@ def process_build(build) # Use upsert since this code can be called more than once for the same build ::Ci::FinishedBuildChSyncEvent.upsert_from_build(build) if finished_build_sync_event?(build) + + ::Ci::Slsa::PublishStatementWorker.perform_async(build.id) if should_perform_attestation?(build) end private @@ -38,6 +40,10 @@ def requirements_available?(build) def finished_build_sync_event?(build) build.is_a?(::Ci::Build) && build.finished_at.present? end + + def should_perform_attestation?(build) + ::Feature.enabled?(:slsa_provenance_statement, build.project) && build.artifacts? + end end end end diff --git a/ee/config/feature_flags/gitlab_com_derisk/slsa_provenance_statement.yml b/ee/config/feature_flags/gitlab_com_derisk/slsa_provenance_statement.yml new file mode 100644 index 0000000000000000000000000000000000000000..2625dabede93e64b11e765ba29c383c53799f34d --- /dev/null +++ b/ee/config/feature_flags/gitlab_com_derisk/slsa_provenance_statement.yml @@ -0,0 +1,10 @@ +--- +name: slsa_provenance_statement +description: Roll out feature flag to publish SLSA provenance statements +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/547865 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198167 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/547866 +milestone: '18.3' +group: group::pipeline security +type: gitlab_com_derisk +default_enabled: false diff --git a/ee/spec/workers/ee/ci/build_finished_worker_spec.rb b/ee/spec/workers/ee/ci/build_finished_worker_spec.rb index 00a14b2baec14f44d8f3adb8c693d6e792675905..8f999dd5866028c7bf4e3e201d1e1411dc092ef0 100644 --- a/ee/spec/workers/ee/ci/build_finished_worker_spec.rb +++ b/ee/spec/workers/ee/ci/build_finished_worker_spec.rb @@ -197,5 +197,46 @@ def project_stats expect { perform }.not_to change { Ci::FinishedBuildChSyncEvent.count } end end + + it 'does not call PublishStatementWorker when build does not have artifacts' do + expect(::Ci::Slsa::PublishStatementWorker).not_to receive(:perform_async).with(build.id) + + perform + end + + context 'when artifacts exist' do + let_it_be(:status) { :success } + let_it_be(:build) do + create(:ee_ci_build, :artifacts, :sast, status, runner: ci_runner, finished_at: 1.hour.ago) + end + + it 'calls PublishStatementWorker when build is successful' do + expect(::Ci::Slsa::PublishStatementWorker).to receive(:perform_async).with(build.id) + + perform + end + + context 'when the build fails' do + let_it_be(:status) { :failed } + + it 'still calls PublishStatementWorker' do + expect(::Ci::Slsa::PublishStatementWorker).to receive(:perform_async).with(build.id) + + perform + end + end + + context 'and flag is disabled' do + before do + stub_feature_flags(slsa_provenance_statement: false) + end + + it 'does not call PublishStatementWorker' do + expect(::Ci::Slsa::PublishStatementWorker).not_to receive(:perform_async).with(build.id) + + perform + end + end + end end end diff --git a/spec/models/ci/slsa/provenance_statement_spec.rb b/spec/models/ci/slsa/provenance_statement_spec.rb index d1700255cbef167b3ebb3a6a06f7c0f29b5ddd22..55cc020e82a7a5c8cc7e45db947f44ea0b4679c2 100644 --- a/spec/models/ci/slsa/provenance_statement_spec.rb +++ b/spec/models/ci/slsa/provenance_statement_spec.rb @@ -106,8 +106,7 @@ let(:build_definition) { parsed['predicate']['buildDefinition'] } it 'has the correct predicate build definition' do - # TODO: update buildType as part of https://gitlab.com/gitlab-org/gitlab/-/issues/426764 - expect(build_definition['buildType']).to eq('https://gitlab.com/gitlab-org/gitlab/-/issues/546150') + expect(build_definition['buildType']).to eq('https://docs.gitlab.com/ci/pipeline_security/slsa/provenance_v1') expect(build_definition['internalParameters']['name']).to start_with("My runner") expect(build_definition['resolvedDependencies'].length).to eq(1) end