Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Unreleased

Nothing Yet!
* Add support for GitHub Attestations in the host phase.

# Version 0.29.0 (2025-07-31)

Expand Down
35 changes: 35 additions & 0 deletions book/src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ We're currently in the middle of [a major config migration](https://github.com/a
* [github hosting settings](#github-hosting-settings)
* [`create-release`](#create-release)
* [`github-attestations`](#github-attestations)
* [`github-attestations-phase`](#github-attestations-phase)
* [`github-attestations-filters`](#github-attestations-filters)
* [`github-release`](#github-release)
* [`github-releases-repo`](#github-releases-repo)
* [`github-releases-submodule-path`](#github-releases-submodule-path)
Expand Down Expand Up @@ -1071,6 +1073,39 @@ These settings govern how we host your files on [GitHub Releases][github-release

If you're using GitHub Releases, this will enable GitHub's experimental artifact attestation feature.

#### `github-attestations-phase`

> <span style="float:right">since 0.30.0<br>[global-only][]</span>
> 🔧 this is an experimental feature! \
> [📖 read the guide for this feature!](../supplychain-security/attestations/github.md) \
> default = `"build-local-artifacts"`
>
> *in your dist-workspace.toml or dist.toml:*
> ```toml
> [dist]
> github-attestations-phase = "host"
> ```

Possible values:

* `host`: Create the GitHub Attestations during the `host` phase.
* `build-local-artifacts`: Create the GitHub Attestations during the `build-local-artifacts` phase (default).


#### `github-attestations-filters`

> <span style="float:right">since 0.30.0<br>[global-only][]</span>
> 🔧 this is an experimental feature! \
> [📖 read the guide for this feature!](../supplychain-security/attestations/github.md) \
> default = `["*"]`
>
> *in your dist-workspace.toml or dist.toml:*
> ```toml
> [dist]
> github-attestations-filters = ["*.json", "*.sh", "*.ps1", "*.zip", "*.tar.gz"]
> ```

Allows filtering GitHub Attestations in the `host` phase. All patterns are globed against the pattern `artifacts/{filter}`.

#### `github-release`

Expand Down
31 changes: 31 additions & 0 deletions book/src/supplychain-security/attestations/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,37 @@ Note that GitHub's Artifact Attestations only supports public repositories and p

Currently, verification of GitHub Artifact Attestations is only supported via GitHub CLI with [`gh attestation verify`].

Additionally, you can control which phase attestations occur using the [`github-attestations-phase` setting](../../reference/config.md#github-attestations-phase).

By default, attestations occur during the `build-local-artifacts` phase. This can be alternatively be changed to the `host` phase, which is particularly
useful when `build-local-artifacts` is set to `false`.

When performing attestations in the `host` phase, you can control what gets attested by using the [`github-attestations-filters` setting](../../reference/config.md#github-attestations-filters).

This setting yields the following attestation step by default:

```yaml
- name: Attest
uses: actions/attest-build-provenance@v2
with:
subject-path: |
artifacts/*
```

When set to a different set of values such as `github-attestations-filters = ["*.json", "*.sh", "*.ps1", "*.zip", "*.tar.gz"]` it yields:

```yaml
- name: Attest
uses: actions/attest-build-provenance@v2
with:
subject-path: |
artifacts/*.json
artifacts/*.sh
artifacts/*.ps1
artifacts/*.zip
artifacts/*.tar.gz
```

[Artifact Attestations]: https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/
[Sigstore]: https://www.sigstore.dev/
[Rekor]: https://docs.sigstore.dev/logging/overview/
Expand Down
64 changes: 64 additions & 0 deletions cargo-dist-schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ pub struct DistManifest {
#[serde(default)]
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub github_attestations: bool,
/// Patterns to attest when creating Artifact Attestations
#[serde(default)]
#[serde(skip_serializing_if = "GithubAttestationsFilters::is_default")]
pub github_attestations_filters: GithubAttestationsFilters,
/// When to generate Artifact Attestations
///
/// Defaults to "build-local-artifacts" for backwards compatibility
#[serde(default)]
#[serde(skip_serializing_if = "GithubAttestationsPhase::is_default")]
pub github_attestations_phase: GithubAttestationsPhase,
}

/// Information about the build environment on this system
Expand Down Expand Up @@ -497,6 +507,58 @@ pub struct GithubLocalJobConfig {
pub cache_provider: Option<String>,
}

/// Used to capture GitHub Attestations filters
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct GithubAttestationsFilters(Vec<String>);

impl Default for GithubAttestationsFilters {
fn default() -> Self {
Self(vec!["*".to_string()])
}
}

impl<'a> IntoIterator for &'a GithubAttestationsFilters {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}

impl GithubAttestationsFilters {
fn is_default(&self) -> bool {
*self == Default::default()
}
}

/// Phase in which to generate GitHub attestations
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub enum GithubAttestationsPhase {
/// Generate attestations during the `host` phase
#[serde(rename = "host")]
Host,
/// Generate attestations during `build-local-artifacts` (default for backwards compatibility)
#[default]
#[serde(rename = "build-local-artifacts")]
BuildLocalArtifacts,
}

impl GithubAttestationsPhase {
fn is_default(&self) -> bool {
matches!(self, GithubAttestationsPhase::BuildLocalArtifacts)
}
}

impl std::fmt::Display for GithubAttestationsPhase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GithubAttestationsPhase::Host => write!(f, "host"),
GithubAttestationsPhase::BuildLocalArtifacts => write!(f, "build-local-artifacts"),
}
}
}

/// A GitHub Actions "run" step, either bash or powershell
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
// this mirrors GHA's structure, see
Expand Down Expand Up @@ -871,6 +933,8 @@ impl DistManifest {
announcement_changelog: None,
announcement_github_body: None,
github_attestations: false,
github_attestations_filters: Default::default(),
github_attestations_phase: Default::default(),
system_info: None,
releases,
artifacts,
Expand Down
42 changes: 42 additions & 0 deletions cargo-dist-schema/src/snapshots/dist_schema__emit.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion cargo-dist/src/backend/ci/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use axoprocess::Cmd;
use camino::{Utf8Path, Utf8PathBuf};
use dist_schema::{
target_lexicon::{self, Architecture, OperatingSystem, Triple},
AptPackageName, ChocolateyPackageName, ContainerImageRef, GhaRunStep, GithubGlobalJobConfig,
AptPackageName, ChocolateyPackageName, ContainerImageRef, GhaRunStep,
GithubAttestationsFilters, GithubAttestationsPhase, GithubGlobalJobConfig,
GithubLocalJobConfig, GithubMatrix, GithubRunnerConfig, GithubRunnerRef, GithubRunners,
HomebrewPackageName, PackageInstallScript, PackageVersion, PipPackageName, TripleNameRef,
};
Expand Down Expand Up @@ -123,6 +124,10 @@ pub struct GithubReleaseInfo {
pub external_repo_commit: Option<String>,
/// Whether to enable GitHub Attestations
pub github_attestations: bool,
/// Patterns to attest when creating attestations for release artifacts
pub github_attestations_filters: GithubAttestationsFilters,
/// When to generate GitHub Attestations
pub github_attestations_phase: GithubAttestationsPhase,
/// `gh` command to run to create the release
pub release_command: String,
/// Which phase to create the release at
Expand Down Expand Up @@ -474,6 +479,8 @@ impl GithubReleaseInfo {
let create_release = host_config.create;
let github_releases_repo = host_config.repo.clone().map(|r| r.into_jinja());
let github_attestations = host_config.attestations;
let github_attestations_filters = host_config.attestations_filters.clone();
let github_attestations_phase = host_config.attestations_phase;

let github_releases_submodule_path = host_config.submodule_path.clone();
let external_repo_commit = github_releases_submodule_path
Expand Down Expand Up @@ -521,6 +528,8 @@ impl GithubReleaseInfo {
github_releases_repo,
external_repo_commit,
github_attestations,
github_attestations_filters,
github_attestations_phase,
release_command,
release_phase,
}))
Expand Down
4 changes: 2 additions & 2 deletions cargo-dist/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use axoasset::{toml_edit, SourceFile};
use axoproject::local_repo::LocalRepo;
use camino::{Utf8Path, Utf8PathBuf};
use dist_schema::{
AptPackageName, ChecksumExtensionRef, ChocolateyPackageName, HomebrewPackageName,
PackageVersion, TripleName, TripleNameRef,
AptPackageName, ChecksumExtensionRef, ChocolateyPackageName, GithubAttestationsFilters,
GithubAttestationsPhase, HomebrewPackageName, PackageVersion, TripleName, TripleNameRef,
};
use serde::{Deserialize, Serialize};

Expand Down
18 changes: 18 additions & 0 deletions cargo-dist/src/config/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,14 @@ pub struct DistMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub github_attestations: Option<bool>,

/// GitHub Attestation filters (default *)
#[serde(skip_serializing_if = "Option::is_none")]
pub github_attestations_filters: Option<GithubAttestationsFilters>,

/// When to generate GitHub Attestations (default build-local-artifacts)
#[serde(skip_serializing_if = "Option::is_none")]
pub github_attestations_phase: Option<GithubAttestationsPhase>,

/// Hosting provider
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, with = "opt_string_or_vec")]
Expand Down Expand Up @@ -619,6 +627,8 @@ impl DistMetadata {
ssldotcom_windows_sign: _,
macos_sign: _,
github_attestations: _,
github_attestations_filters: _,
github_attestations_phase: _,
msvc_crt_static: _,
hosting: _,
github_custom_runners: _,
Expand Down Expand Up @@ -723,6 +733,8 @@ impl DistMetadata {
ssldotcom_windows_sign,
macos_sign,
github_attestations,
github_attestations_filters,
github_attestations_phase,
msvc_crt_static,
hosting,
extra_artifacts,
Expand Down Expand Up @@ -814,6 +826,12 @@ impl DistMetadata {
if github_attestations.is_some() {
warn!("package.metadata.dist.github-attestations is set, but this is only accepted in workspace.metadata (value is being ignored): {}", package_manifest_path);
}
if github_attestations_filters.is_some() {
warn!("package.metadata.dist.github-attestations-filters is set, but this is only accepted in workspace.metadata (value is being ignored): {}", package_manifest_path);
}
if github_attestations_phase.is_some() {
warn!("package.metadata.dist.github-attestations-phase is set, but this is only accepted in workspace.metadata (value is being ignored): {}", package_manifest_path);
}
if msvc_crt_static.is_some() {
warn!("package.metadata.dist.msvc-crt-static is set, but this is only accepted in workspace.metadata (value is being ignored): {}", package_manifest_path);
}
Expand Down
4 changes: 4 additions & 0 deletions cargo-dist/src/config/v0_to_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ impl DistMetadata {
macos_sign,
mac_pkg_config,
github_attestations,
github_attestations_filters,
github_attestations_phase,
hosting,
extra_artifacts,
github_custom_runners,
Expand Down Expand Up @@ -247,6 +249,8 @@ impl DistMetadata {
submodule_path: github_releases_submodule_path.map(|p| p.into()),
during: github_release,
attestations: github_attestations,
attestations_filters: github_attestations_filters,
attestations_phase: github_attestations_phase,
})
} else {
None
Expand Down
Loading
Loading