Skip to content

Commit 3a0a5f0

Browse files
authored
Use github artifact attestations for the prebuilt artifacts (#503)
* Add optional verification of prebuilt artifacts Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> f Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * CI: Enable strict attestation check in release-check Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * CI: Verify ohos archives in release-check. Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * Disable artifact verification by default Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * Move attestation into function Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * fix ohos archive ci verification Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * Bump version to 128.3-3 Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> --------- Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
1 parent 1179f5b commit 3a0a5f0

File tree

4 files changed

+169
-2
lines changed

4 files changed

+169
-2
lines changed

.github/workflows/release-check.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,47 @@ jobs:
4444
cargo test --tests --examples --verbose --features streams
4545
- name: Build from auto-download
4646
if: ${{ env.RELEASE_TAG != '' }}
47+
env:
48+
MOZJS_ATTESTATION: strict
4749
run: |
4850
cargo build --verbose --features streams
4951
cargo test --tests --examples --verbose --features streams
52+
53+
verify-archive-ohos:
54+
name: "Verify archive OpenHarmony"
55+
runs-on: ubuntu-latest
56+
strategy:
57+
matrix:
58+
target: ["aarch64-unknown-linux-ohos", "x86_64-unknown-linux-ohos"]
59+
env:
60+
RELEASE_TAG: ${{ github.event_name == 'release' && github.ref_name || inputs.release-tag }}
61+
steps:
62+
- uses: actions/checkout@v4
63+
- name: Setup OpenHarmony SDK
64+
id: setup_sdk
65+
uses: openharmony-rs/setup-ohos-sdk@v0.1
66+
with:
67+
version: "4.1"
68+
- name: Install Rust
69+
uses: dtolnay/rust-toolchain@stable
70+
with:
71+
targets: ${{ matrix.target }}
72+
- name: Download prebuilt mozjs from artifact
73+
if: ${{ env.RELEASE_TAG == '' }}
74+
uses: actions/download-artifact@v4
75+
with:
76+
name: libmozjs-${{ matrix.target }}.tar.gz
77+
- name: Build from archive
78+
if: ${{ env.RELEASE_TAG == '' }}
79+
env:
80+
OHOS_SDK_NATIVE: ${{ steps.setup_sdk.outputs.ohos_sdk_native }}
81+
MOZJS_ARCHIVE: libmozjs-${{ matrix.target }}.tar.gz
82+
run: |
83+
./ohos-build cargo build --target="${{ matrix.target }}" --verbose --features streams
84+
- name: Build from auto-download (arch ${{ matrix.target }})
85+
if: ${{ env.RELEASE_TAG != '' }}
86+
env:
87+
OHOS_SDK_NATIVE: ${{ steps.setup_sdk.outputs.ohos_sdk_native }}
88+
MOZJS_ATTESTATION: strict
89+
run: |
90+
./ohos-build cargo build --target="${{ matrix.target }}" --verbose --features "streams"

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ this feature:
2727
archive. The base URL should be similar to `https://github.com/servo/mozjs/releases`.
2828
The build script will append the version and target accordingly. See the files at the example
2929
URL for more details.
30+
- `MOZJS_ATTESTATION` allows uses [Github Attestations] to verify the integrity of the prebuilt archive
31+
and that the archive was built by in CI, for a valid commit on the main branch of the servo/mozjs repo.
32+
Attestation verification requires having a recent version of the github cli tool [gh] installed.
33+
If artifact verification is enabled and reports an error, the prebuilt archive will be discarded and
34+
mozjs will be built from source instead.
35+
Available values are:
36+
- unset (default): Equivalent to `off`.
37+
- `MOZJS_ATTESTATION=<0|false|off>`: Disable artifact verification.
38+
- `MOZJS_ATTESTATION=<1|true|on|lenient>`: Enable artifact verification and fallback to compiling from source if
39+
verification fails or is not possible.
40+
- `MOZJS_ATTESTATION=<2|strict|force>`: Fail the build if artifact verification fails.
41+
42+
[Github Attestations]: https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds
43+
[gh]: https://cli.github.com/
3044

3145
## Building from Source
3246

mozjs-sys/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "mozjs_sys"
33
description = "System crate for the Mozilla SpiderMonkey JavaScript engine."
44
repository.workspace = true
5-
version = "0.128.3-2"
5+
version = "0.128.3-3"
66
authors = ["Mozilla"]
77
links = "mozjs"
88
build = "build.rs"

mozjs-sys/build.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ use flate2::read::GzDecoder;
77
use flate2::write::GzEncoder;
88
use flate2::Compression;
99
use std::env;
10+
use std::env::VarError;
1011
use std::ffi::{OsStr, OsString};
1112
use std::fs;
1213
use std::fs::File;
1314
use std::path::{Path, PathBuf};
1415
use std::process::Command;
1516
use std::str;
17+
use std::sync::LazyLock;
18+
use std::time::Instant;
1619
use tar::Archive;
1720
use walkdir::WalkDir;
1821

@@ -905,6 +908,106 @@ fn decompress_static_lib(archive: &Path, build_dir: &Path) -> Result<(), std::io
905908
Ok(())
906909
}
907910

911+
static ATTESTATION_AVAILABLE: LazyLock<bool> = LazyLock::new(|| {
912+
Command::new("gh")
913+
.arg("attestation")
914+
.arg("--help")
915+
.output()
916+
.is_ok_and(|output| output.status.success())
917+
});
918+
919+
enum AttestationType {
920+
/// Fallback to compiling from source on failure
921+
Lenient,
922+
/// Abort the build on failure
923+
Strict,
924+
}
925+
926+
enum ArtifactAttestation {
927+
/// Do not verify the attestation artifact.
928+
Disabled,
929+
/// Verify the attestation artifact
930+
Enabled(AttestationType),
931+
}
932+
933+
impl ArtifactAttestation {
934+
const ENV_VAR_NAME: &'static str = "MOZJS_ATTESTATION";
935+
936+
fn from_env_str(value: &str) -> Self {
937+
match value {
938+
"0" | "off" | "false" => ArtifactAttestation::Disabled,
939+
"1" | "on" | "true" | "lenient" => {
940+
ArtifactAttestation::Enabled(AttestationType::Lenient)
941+
}
942+
"2" | "strict" | "force" => ArtifactAttestation::Enabled(AttestationType::Strict),
943+
other => {
944+
println!(
945+
"cargo:warning=`{}` set to unsupported value: {other}",
946+
Self::ENV_VAR_NAME
947+
);
948+
ArtifactAttestation::Enabled(AttestationType::Lenient)
949+
}
950+
}
951+
}
952+
953+
fn from_env() -> Self {
954+
match env::var(Self::ENV_VAR_NAME) {
955+
Ok(value) => {
956+
let lower = value.to_lowercase();
957+
return Self::from_env_str(&lower);
958+
}
959+
Err(VarError::NotPresent) => {}
960+
Err(VarError::NotUnicode(_)) => {
961+
println!(
962+
"cargo:warning={} value must be valid unicode.",
963+
Self::ENV_VAR_NAME
964+
);
965+
}
966+
}
967+
ArtifactAttestation::Disabled
968+
}
969+
}
970+
971+
/// Use GitHub artifact attestation to verify the artifact is not corrupt.
972+
fn attest_artifact(kind: AttestationType, archive_path: &Path) -> Result<(), std::io::Error> {
973+
let start = Instant::now();
974+
if !*ATTESTATION_AVAILABLE {
975+
println!(
976+
"cargo:warning=Artifact attestation enabled, but not available. \
977+
Please refer to the documentation for available values for {}",
978+
ArtifactAttestation::ENV_VAR_NAME
979+
);
980+
}
981+
let mut attestation_cmd = Command::new("gh");
982+
attestation_cmd
983+
.arg("attestation")
984+
.arg("verify")
985+
.arg(&archive_path)
986+
.arg("-R")
987+
.arg("servo/mozjs");
988+
989+
let attestation_duration = start.elapsed();
990+
eprintln!(
991+
"Artifact evaluation took {} ms",
992+
attestation_duration.as_millis()
993+
);
994+
995+
if let Err(output) = attestation_cmd.output() {
996+
println!("cargo:warning=Failed to verify the artifact downloaded from CI: {output:?}");
997+
// Remove the file so the build-script will redownload next time.
998+
let _ = fs::remove_file(&archive_path).inspect_err(|e| {
999+
println!("cargo:warning=Failed to delete archive: {e}");
1000+
});
1001+
match kind {
1002+
AttestationType::Strict => panic!("Artifact verification failed!"),
1003+
AttestationType::Lenient => {
1004+
return Err(std::io::Error::from(std::io::ErrorKind::InvalidData));
1005+
}
1006+
}
1007+
}
1008+
Ok(())
1009+
}
1010+
9081011
/// Download the SpiderMonkey archive with cURL using the provided base URL. If it's None,
9091012
/// it will use `servo/mozjs`'s release page as the base URL.
9101013
fn download_archive(base: Option<&str>) -> Result<PathBuf, std::io::Error> {
@@ -913,6 +1016,8 @@ fn download_archive(base: Option<&str>) -> Result<PathBuf, std::io::Error> {
9131016
let target = env::var("TARGET").unwrap();
9141017
let archive_path = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("libmozjs.tar.gz");
9151018
if !archive_path.exists() {
1019+
eprintln!("Trying to download prebuilt mozjs static library from Github Releases");
1020+
let curl_start = Instant::now();
9161021
if !Command::new("curl")
9171022
.arg("-L")
9181023
.arg("-f")
@@ -927,8 +1032,15 @@ fn download_archive(base: Option<&str>) -> Result<PathBuf, std::io::Error> {
9271032
{
9281033
return Err(std::io::Error::from(std::io::ErrorKind::NotFound));
9291034
}
1035+
eprintln!(
1036+
"Successfully downloaded mozjs archive in {} ms",
1037+
curl_start.elapsed().as_millis()
1038+
);
1039+
let attestation = ArtifactAttestation::from_env();
1040+
if let ArtifactAttestation::Enabled(kind) = attestation {
1041+
attest_artifact(kind, &archive_path)?;
1042+
}
9301043
}
931-
9321044
Ok(archive_path)
9331045
}
9341046

0 commit comments

Comments
 (0)