Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebrand 'canonical' to 'stable' for artifacts #134

Merged
merged 1 commit into from
Oct 29, 2024
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
21 changes: 11 additions & 10 deletions docs/builds/[email protected]
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# `ArtifactEquivalence` Build Type

The artifact equivalence attestation is a claim that two artifacts are equal
after some non-security relevant details have been normalized.
after certain non-security relevant aspects have been stabilized (see
[section below](#artifact-stabilization-details)).

Rebuilding exact bit-for-bit identical copies of upstream artifacts is not
always possible. However, in many cases, the only reason a bit-for-bit match
Expand Down Expand Up @@ -85,7 +86,7 @@ Example:

### Byproducts

The `byproducts` include a hash digest of the normalized version.
The `byproducts` include a hash digest of the stabilized version.

| field | details |
| -------- | ----------------------------------------------------------- |
Expand All @@ -105,21 +106,21 @@ Example:
]
```

## Normalization Details
## Artifact Stabilization Details

To compare the rebuilt artifact and the upstream artifact, OSS Rebuild puts both
artifacts through a normalization process and compares the results. If the
rebuild was successful, then the outcome of this process for both upstream and
rebuild should result in an identical "normalized" artifact.
artifacts through a stabilization process and compares the results. If the
rebuild was successful, then the result of this process for both upstream and
rebuild should be identical artifacts.

### Zip

[Zip](<https://en.wikipedia.org/wiki/ZIP_(file_format)>) is an archive file
format that supports lossless data compression. Zip archives contain
modification times, zip version metadata and other filesystem specific data
frequently differ from system to system. We believe this data does not have a
modification times, zip version metadata, and other filesystem specific data
that frequently differ from system to system. We believe this data does not have a
meaningful security impact for the source-based distribution systems like those
supported by OSS Rebuild. For zip based archives, this is the normalization
supported by OSS Rebuild. For zip based archives, this is the stabilization
process:

1. Read all the existing zip entries
Expand All @@ -138,7 +139,7 @@ that is done using another compression scheme in combination with tar. Tarballs
contain the file mode, owner and group IDs, and a modification time. These
frequently differ between build environments and we do not believe they have a
meaningful security impact for the source-based distribution systems like those
supported by OSS Rebuild. For tar based archives, this is the normalization
supported by OSS Rebuild. For tar based archives, this is the stabilization
process:

1. Read all the existing tar entries
Expand Down
11 changes: 6 additions & 5 deletions docs/builds/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,13 @@ Example:

### Byproducts

The `byproducts` include a hash digest of the normalized version.
The `byproducts` include the full file constructs used produce the artifact
such as the high-level definition, the Cloud Build definition, and the specific Dockerfile.

| field | details |
| --------- | --------------------------------------------------------------------------------------------------------- |
| `name` | The high-level build definition, Dockerfile, and Google Cloud Build process that implemented the rebuild. |
| `content` | The base64-encoded content of the artifact. |
| field | details |
| --------- | -------------------------------------------------------- |
| `name` | The resource identifier for the build process byproduct. |
| `content` | The base64-encoded content of the artifact. |

Example:

Expand Down
4 changes: 2 additions & 2 deletions internal/api/apiservice/rebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ func buildAndAttest(ctx context.Context, deps *RebuildPackageDeps, mux rebuild.R
return errors.Wrap(err, "comparing artifacts")
}
exactMatch := bytes.Equal(rb.Hash.Sum(nil), up.Hash.Sum(nil))
canonicalizedMatch := bytes.Equal(rb.CanonicalHash.Sum(nil), up.CanonicalHash.Sum(nil))
if !exactMatch && !canonicalizedMatch {
stabilizedMatch := bytes.Equal(rb.StabilizedHash.Sum(nil), up.StabilizedHash.Sum(nil))
if !exactMatch && !stabilizedMatch {
return api.AsStatus(codes.FailedPrecondition, errors.New("rebuild content mismatch"))
}
input := rebuild.Input{Target: t}
Expand Down
2 changes: 1 addition & 1 deletion internal/verifier/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func CreateAttestations(ctx context.Context, input rebuild.Input, finalStrategy
InvocationID: id,
},
Byproducts: []slsa1.ResourceDescriptor{
{Name: publicNormalizedURI, Digest: makeDigestSet(up.CanonicalHash...)},
{Name: publicNormalizedURI, Digest: makeDigestSet(up.StabilizedHash...)},
},
},
},
Expand Down
12 changes: 6 additions & 6 deletions internal/verifier/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ func TestCreateAttestations(t *testing.T) {
ctx := context.Background()
target := rebuild.Target{Ecosystem: rebuild.CratesIO, Package: "bytes", Version: "1.0.0", Artifact: "bytes-1.0.0.crate"}
rbSummary := ArtifactSummary{
URI: "gs://rebuild.bucket/bytes-1.0.0.crate",
Hash: hashext.NewMultiHash(crypto.SHA256),
CanonicalHash: hashext.NewMultiHash(crypto.SHA256),
URI: "gs://rebuild.bucket/bytes-1.0.0.crate",
Hash: hashext.NewMultiHash(crypto.SHA256),
StabilizedHash: hashext.NewMultiHash(crypto.SHA256),
}
upSummary := ArtifactSummary{
URI: "https://up.stream/bytes-1.0.0.crate",
Hash: hashext.NewMultiHash(crypto.SHA256),
CanonicalHash: hashext.NewMultiHash(crypto.SHA256),
URI: "https://up.stream/bytes-1.0.0.crate",
Hash: hashext.NewMultiHash(crypto.SHA256),
StabilizedHash: hashext.NewMultiHash(crypto.SHA256),
}
buildInfo := &rebuild.BuildInfo{
Target: target,
Expand Down
14 changes: 7 additions & 7 deletions internal/verifier/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ import (

// ArtifactSummary is a summary of an artifact for the purposes of verification.
type ArtifactSummary struct {
URI string
Hash hashext.MultiHash
CanonicalHash hashext.MultiHash
URI string
Hash hashext.MultiHash
StabilizedHash hashext.MultiHash
}

// SummarizeArtifacts fetches and summarizes the rebuild and upstream artifacts.
func SummarizeArtifacts(ctx context.Context, metadata rebuild.LocatableAssetStore, t rebuild.Target, upstreamURI string, hashes []crypto.Hash) (rb, up ArtifactSummary, err error) {
rb = ArtifactSummary{Hash: hashext.NewMultiHash(hashes...), CanonicalHash: hashext.NewMultiHash(hashes...)}
up = ArtifactSummary{Hash: hashext.NewMultiHash(hashes...), CanonicalHash: hashext.NewMultiHash(hashes...), URI: upstreamURI}
rb = ArtifactSummary{Hash: hashext.NewMultiHash(hashes...), StabilizedHash: hashext.NewMultiHash(hashes...)}
up = ArtifactSummary{Hash: hashext.NewMultiHash(hashes...), StabilizedHash: hashext.NewMultiHash(hashes...), URI: upstreamURI}
// Fetch and process rebuild.
var r io.ReadCloser
rbAsset := rebuild.Asset{Target: t, Type: rebuild.RebuildAsset}
Expand All @@ -48,7 +48,7 @@ func SummarizeArtifacts(ctx context.Context, metadata rebuild.LocatableAssetStor
return
}
defer checkClose(r)
err = archive.Canonicalize(rb.CanonicalHash, io.TeeReader(r, rb.Hash), t.ArchiveType())
err = archive.Stabilize(rb.StabilizedHash, io.TeeReader(r, rb.Hash), t.ArchiveType())
if err != nil {
err = errors.Wrap(err, "fingerprinting rebuild")
return
Expand All @@ -64,7 +64,7 @@ func SummarizeArtifacts(ctx context.Context, metadata rebuild.LocatableAssetStor
err = errors.Errorf("non-OK status fetching upstream artifact")
return
}
err = archive.Canonicalize(up.CanonicalHash, io.TeeReader(resp.Body, up.Hash), t.ArchiveType())
err = archive.Stabilize(up.StabilizedHash, io.TeeReader(resp.Body, up.Hash), t.ArchiveType())
checkClose(resp.Body)
if err != nil {
err = errors.Wrap(err, "fingerprinting upstream")
Expand Down
14 changes: 7 additions & 7 deletions internal/verifier/summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ func TestSummarizeArtifacts(t *testing.T) {
}
},
})
canonicalizedHash := hashext.NewMultiHash(crypto.SHA256)
canonicalizedZip := must(archivetest.ZipFile([]archive.ZipEntry{
stabilizedHash := hashext.NewMultiHash(crypto.SHA256)
stabilizedZip := must(archivetest.ZipFile([]archive.ZipEntry{
{FileHeader: &zip.FileHeader{Name: "foo-0.0.1.dist-info/WHEEL", Modified: time.UnixMilli(0)}, Body: []byte("data")},
}))
must(canonicalizedHash.Write(canonicalizedZip.Bytes()))
must(stabilizedHash.Write(stabilizedZip.Bytes()))
rb, up, err := SummarizeArtifacts(ctx, metadata, target, upstreamURI, []crypto.Hash{crypto.SHA256})
if err != nil {
t.Fatalf("SummarizeArtifacts() returned error: %v", err)
Expand All @@ -83,17 +83,17 @@ func TestSummarizeArtifacts(t *testing.T) {
if diff := cmp.Diff(origHash.Sum(nil), rb.Hash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for rb.Hash (-want +got):\n%s", diff)
}
if diff := cmp.Diff(canonicalizedHash.Sum(nil), rb.CanonicalHash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for rb.CanonicalHash (-want +got):\n%s", diff)
if diff := cmp.Diff(stabilizedHash.Sum(nil), rb.StabilizedHash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for rb.StabilizedHash (-want +got):\n%s", diff)
}
if up.URI != upstreamURI {
t.Errorf("SummarizeArtifacts() returned diff for up.URI: want %q, got %q", upstreamURI, up.URI)
}
if diff := cmp.Diff(origHash.Sum(nil), up.Hash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for up.Hash (-want +got):\n%s", diff)
}
if diff := cmp.Diff(canonicalizedHash.Sum(nil), up.CanonicalHash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for up.CanonicalHash (-want +got):\n%s", diff)
if diff := cmp.Diff(stabilizedHash.Sum(nil), up.StabilizedHash.Sum(nil)); diff != "" {
t.Errorf("SummarizeArtifacts() returned diff for up.StabilizedHash (-want +got):\n%s", diff)
}
})
}
Expand Down
12 changes: 6 additions & 6 deletions pkg/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
"github.com/pkg/errors"
)

// Canonicalize selects and applies the canonicalization routine for the given archive format.
func Canonicalize(dst io.Writer, src io.Reader, f Format) error {
// Stabilize selects and applies the stabilization routine for the given archive format.
func Stabilize(dst io.Writer, src io.Reader, f Format) error {
switch f {
case ZipFormat:
srcReader, size, err := toZipCompatibleReader(src)
Expand All @@ -37,9 +37,9 @@ func Canonicalize(dst io.Writer, src io.Reader, f Format) error {
}
zw := zip.NewWriter(dst)
defer zw.Close()
err = CanonicalizeZip(zr, zw)
err = StabilizeZip(zr, zw)
if err != nil {
return errors.Wrap(err, "canonicalizing zip")
return errors.Wrap(err, "stabilizing zip")
}
case TarGzFormat:
gzr, err := gzip.NewReader(src)
Expand All @@ -49,9 +49,9 @@ func Canonicalize(dst io.Writer, src io.Reader, f Format) error {
defer gzr.Close()
gzw := gzip.NewWriter(dst)
defer gzw.Close()
err = CanonicalizeTar(tar.NewReader(gzr), tar.NewWriter(gzw))
err = StabilizeTar(tar.NewReader(gzr), tar.NewWriter(gzw))
if err != nil {
return errors.Wrap(err, "canonicalizing tar")
return errors.Wrap(err, "stabilizing tar")
}
default:
return errors.New("unsupported archive type")
Expand Down
10 changes: 5 additions & 5 deletions pkg/archive/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
// Source: https://github.com/npm/pacote/blob/main/lib/util/tar-create-options.js#L28
var arbitraryTime = time.Date(1985, time.October, 26, 8, 15, 0, 0, time.UTC)

func canonicalizeTarHeader(h *tar.Header) (*tar.Header, error) {
func stabilizeTarHeader(h *tar.Header) (*tar.Header, error) {
switch h.Typeflag {
case tar.TypeGNUSparse, tar.TypeGNULongName, tar.TypeGNULongLink:
// NOTE: Non-PAX header type support can be added, if necessary.
Expand Down Expand Up @@ -76,8 +76,8 @@ func (e TarEntry) WriteTo(tw *tar.Writer) error {
return nil
}

// CanonicalizeTar strips volatile metadata and re-writes the provided archive in a canonical form.
func CanonicalizeTar(tr *tar.Reader, tw *tar.Writer) error {
// StabilizeTar strips volatile metadata and re-writes the provided archive in a standard form.
func StabilizeTar(tr *tar.Reader, tw *tar.Writer) error {
defer tw.Close()
var ents []TarEntry
for {
Expand All @@ -88,7 +88,7 @@ func CanonicalizeTar(tr *tar.Reader, tw *tar.Writer) error {
}
return err
}
canonicalized, err := canonicalizeTarHeader(header)
stabilized, err := stabilizeTarHeader(header)
if err != nil {
return err
}
Expand All @@ -98,7 +98,7 @@ func CanonicalizeTar(tr *tar.Reader, tw *tar.Writer) error {
}
// TODO: Memory-intensive. We're buffering the full file in memory (again).
// One option would be to do two passes and only buffer what's necessary.
ents = append(ents, TarEntry{canonicalized, buf[:]})
ents = append(ents, TarEntry{stabilized, buf[:]})
}
sort.Slice(ents, func(i, j int) bool {
return ents[i].Header.Name < ents[j].Header.Name
Expand Down
10 changes: 5 additions & 5 deletions pkg/archive/tar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/google/go-cmp/cmp"
)

func TestCanonicalizeTar(t *testing.T) {
func TestStabilizeTar(t *testing.T) {
testCases := []struct {
test string
input []*TarEntry
Expand Down Expand Up @@ -77,9 +77,9 @@ func TestCanonicalizeTar(t *testing.T) {
}
var output bytes.Buffer
zr := tar.NewReader(bytes.NewReader(input.Bytes()))
err := CanonicalizeTar(zr, tar.NewWriter(&output))
err := StabilizeTar(zr, tar.NewWriter(&output))
if err != nil {
t.Fatalf("CanonicalizeTar(%v) = %v, want nil", tc.test, err)
t.Fatalf("StabilizeTar(%v) = %v, want nil", tc.test, err)
}
var got []*TarEntry
{
Expand All @@ -94,10 +94,10 @@ func TestCanonicalizeTar(t *testing.T) {
}
}
if len(got) != len(tc.expected) {
t.Fatalf("CanonicalizeTar(%v) = %v, want %v", tc.test, got, tc.expected)
t.Fatalf("StabilizeTar(%v) = %v, want %v", tc.test, got, tc.expected)
}
if !cmp.Equal(got, tc.expected) {
t.Fatalf("CanonicalizeTar(%v) = %v, want %v\nDiff:\n%s", tc.test, got, tc.expected, cmp.Diff(got, tc.expected))
t.Fatalf("StabilizeTar(%v) = %v, want %v\nDiff:\n%s", tc.test, got, tc.expected, cmp.Diff(got, tc.expected))
}
})
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/archive/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ func (e ZipEntry) WriteTo(zw *zip.Writer) error {
return nil
}

// CanonicalizeZip strips volatile metadata and rewrites the provided archive in a canonical form.
func CanonicalizeZip(zr *zip.Reader, zw *zip.Writer) error {
// StabilizeZip strips volatile metadata and rewrites the provided archive in a standard form.
func StabilizeZip(zr *zip.Reader, zw *zip.Writer) error {
defer zw.Close()
var ents []ZipEntry
for _, f := range zr.File {
Expand Down
10 changes: 5 additions & 5 deletions pkg/archive/zip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"time"
)

func TestCanonicalizeZip(t *testing.T) {
func TestStabilizeZip(t *testing.T) {
testCases := []struct {
test string
input []*ZipEntry
Expand Down Expand Up @@ -83,9 +83,9 @@ func TestCanonicalizeZip(t *testing.T) {
}
var output bytes.Buffer
zr := must(zip.NewReader(bytes.NewReader(input.Bytes()), int64(input.Len())))
err := CanonicalizeZip(zr, zip.NewWriter(&output))
err := StabilizeZip(zr, zip.NewWriter(&output))
if err != nil {
t.Fatalf("CanonicalizeZip(%v) = %v, want nil", tc.test, err)
t.Fatalf("StabilizeZip(%v) = %v, want nil", tc.test, err)
}
var got []ZipEntry
{
Expand All @@ -95,7 +95,7 @@ func TestCanonicalizeZip(t *testing.T) {
}
}
if len(got) != len(tc.expected) {
t.Fatalf("CanonicalizeZip(%v) = %v, want %v", tc.test, got, tc.expected)
t.Fatalf("StabilizeZip(%v) = %v, want %v", tc.test, got, tc.expected)
}
for i := range got {
if !all(
Expand All @@ -104,7 +104,7 @@ func TestCanonicalizeZip(t *testing.T) {
got[i].FileHeader.Modified.Equal(tc.expected[i].FileHeader.Modified),
got[i].FileHeader.Comment == tc.expected[i].FileHeader.Comment,
) {
t.Fatalf("CanonicalizeZip(%v) = %v, want %v", tc.test, got, tc.expected)
t.Fatalf("StabilizeZip(%v) = %v, want %v", tc.test, got, tc.expected)
}
}
})
Expand Down
16 changes: 8 additions & 8 deletions pkg/rebuild/rebuild/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ func artifactReader(ctx context.Context, t Target, mux RegistryMux) (io.ReadClos
}
}

// Canonicalize canonicalizes the upstream and rebuilt artifacts.
func Canonicalize(ctx context.Context, t Target, mux RegistryMux, rbPath string, fs billy.Filesystem, assets AssetStore) (rb, up Asset, err error) {
{ // Canonicalize rebuild
// Stabilize the upstream and rebuilt artifacts.
func Stabilize(ctx context.Context, t Target, mux RegistryMux, rbPath string, fs billy.Filesystem, assets AssetStore) (rb, up Asset, err error) {
{ // Stabilize rebuild
rb = Asset{Type: DebugRebuildAsset, Target: t}
w, err := assets.Writer(ctx, rb)
if err != nil {
Expand All @@ -52,11 +52,11 @@ func Canonicalize(ctx context.Context, t Target, mux RegistryMux, rbPath string,
return rb, up, errors.Wrapf(err, "[INTERNAL] Failed to find rebuilt artifact")
}
defer f.Close()
if err := archive.Canonicalize(w, f, t.ArchiveType()); err != nil {
return rb, up, errors.Wrapf(err, "[INTERNAL] Canonicalizing rebuild failed")
if err := archive.Stabilize(w, f, t.ArchiveType()); err != nil {
return rb, up, errors.Wrapf(err, "[INTERNAL] Stabilize rebuild failed")
}
}
{ // Canonicalize upstream
{ // Stabilize upstream
up = Asset{Type: DebugUpstreamAsset, Target: t}
w, err := assets.Writer(ctx, up)
if err != nil {
Expand All @@ -68,8 +68,8 @@ func Canonicalize(ctx context.Context, t Target, mux RegistryMux, rbPath string,
return rb, up, errors.Wrapf(err, "[INTERNAL] Failed to fetch upstream artifact")
}
defer r.Close()
if err := archive.Canonicalize(w, r, t.ArchiveType()); err != nil {
return rb, up, errors.Wrapf(err, "[INTERNAL] Canonicalizing upstream failed")
if err := archive.Stabilize(w, r, t.ArchiveType()); err != nil {
return rb, up, errors.Wrapf(err, "[INTERNAL] Stabilize upstream failed")
}
}
return rb, up, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/rebuild/rebuild/rebuildone.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func RebuildOne(ctx context.Context, r Rebuilder, input Input, mux RegistryMux,
}
return nil, nil, errors.Wrapf(err, "failed to stat artifact")
}
rb, up, err := Canonicalize(ctx, t, mux, rbPath, fs, assets)
rb, up, err := Stabilize(ctx, t, mux, rbPath, fs, assets)
if err != nil {
return nil, nil, err
}
Expand Down