Skip to content

Commit

Permalink
Merge pull request #1788 from imjasonh/rebuild-sbom
Browse files Browse the repository at this point in the history
rebuild: make SBOM more reproducible
  • Loading branch information
imjasonh authored Feb 14, 2025
2 parents 45210a6 + 19cf9ee commit da65599
Showing 1 changed file with 44 additions and 20 deletions.
64 changes: 44 additions & 20 deletions pkg/cli/rebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ package cli
import (
"archive/tar"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"time"

goapk "chainguard.dev/apko/pkg/apk/apk"
apko_types "chainguard.dev/apko/pkg/build/types"
"chainguard.dev/apko/pkg/sbom/generator/spdx"
"chainguard.dev/melange/pkg/build"
"chainguard.dev/melange/pkg/config"
"chainguard.dev/melange/pkg/container/docker"
Expand Down Expand Up @@ -41,7 +43,7 @@ func rebuild() *cobra.Command {
}

for _, a := range args {
cfg, pkginfo, err := getConfig(a)
cfg, pkginfo, cfgpkg, err := getConfig(a)
if err != nil {
return fmt.Errorf("failed to get config for %s: %v", a, err)
}
Expand All @@ -50,21 +52,23 @@ func rebuild() *cobra.Command {
cfg.Environment.Contents.RuntimeRepositories = append(cfg.Environment.Contents.RuntimeRepositories, "https://packages.wolfi.dev/os")
cfg.Environment.Contents.Keyring = append(cfg.Environment.Contents.Keyring, "https://packages.wolfi.dev/os/wolfi-signing.rsa.pub")

f, err := os.CreateTemp("", "melange-rebuild-*.")
f, err := os.Create(fmt.Sprintf("%s.yaml", cfg.Package.Name))
if err != nil {
return fmt.Errorf("failed to create temporary file: %v", err)
}
if err := yaml.NewEncoder(f).Encode(cfg); err != nil {
return fmt.Errorf("failed to encode stripped config: %v", err)
}
log.Println("wrote stripped config to", f.Name())
defer f.Close()
defer os.Remove(f.Name())

if err := BuildCmd(ctx,
[]apko_types.Architecture{apko_types.Architecture("amd64")}, // TODO configurable, or detect
build.WithConfigFileRepositoryURL("https://github.com/wolfi-dev/os"), // TODO get this from the package SBOM
build.WithConfigFileRepositoryCommit("TODO"), // TODO get this from the package SBOM
build.WithConfigFileLicense("Apache-2.0"), // TODO get this from the package SBOM
build.WithBuildDate(time.Unix(pkginfo.BuildDate, 0).Format(time.RFC3339)),
build.WithNamespace("wolfi"), // TODO get this from the package SBOM
build.WithConfigFileRepositoryCommit(cfgpkg.Version),
build.WithConfigFileLicense(cfgpkg.LicenseDeclared),
build.WithBuildDate(time.Unix(pkginfo.BuildDate, 0).UTC().Format(time.RFC3339)),
build.WithRunner(r), // TODO configurable
build.WithConfig(f.Name())); err != nil {
return fmt.Errorf("failed to rebuild %q: %v", a, err)
Expand All @@ -76,60 +80,80 @@ func rebuild() *cobra.Command {
}
}

func getConfig(fn string) (*config.Configuration, *goapk.PackageInfo, error) {
func getConfig(fn string) (*config.Configuration, *goapk.PackageInfo, *spdx.Package, error) {
f, err := os.Open(fn)
if err != nil {
return nil, nil, fmt.Errorf("failed to open file %s: %v", fn, err)
return nil, nil, nil, fmt.Errorf("failed to open file %s: %v", fn, err)
}
defer f.Close()

var cfg *config.Configuration
var pkginfo *goapk.PackageInfo
var cfgpkg *spdx.Package

gz, err := gzip.NewReader(f)
if err != nil {
return nil, nil, fmt.Errorf("failed to create gzip reader: %v", err)
return nil, nil, nil, fmt.Errorf("failed to create gzip reader: %v", err)
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
if cfg == nil {
return nil, nil, fmt.Errorf("failed to find .melange.yaml in %s", fn)
return nil, nil, nil, fmt.Errorf("failed to find .melange.yaml in %s", fn)
}
if pkginfo == nil {
return nil, nil, fmt.Errorf("failed to find .PKGINFO in %s", fn)
return nil, nil, nil, fmt.Errorf("failed to find .PKGINFO in %s", fn)
}
return nil, nil, fmt.Errorf("failed to find necessary rebuild information in %s", fn)
if cfgpkg == nil {
return nil, nil, nil, fmt.Errorf("failed to find SBOM in %s", fn)
}
return nil, nil, nil, fmt.Errorf("failed to find necessary rebuild information in %s", fn)
} else if err != nil {
return nil, nil, fmt.Errorf("failed to read tar header: %v", err)
return nil, nil, nil, fmt.Errorf("failed to read tar header: %v", err)
}

switch hdr.Name {
case ".melange.yaml":
cfg = new(config.Configuration)
if err := yaml.NewDecoder(io.LimitReader(tr, hdr.Size)).Decode(cfg); err != nil {
return nil, nil, fmt.Errorf("failed to decode .melange.yaml: %v", err)
return nil, nil, nil, fmt.Errorf("failed to decode .melange.yaml: %v", err)
}

case ".PKGINFO":
i, err := ini.ShadowLoad(tr)
if err != nil {
return nil, nil, fmt.Errorf("failed to load .PKGINFO: %v", err)
return nil, nil, nil, fmt.Errorf("failed to load .PKGINFO: %v", err)
}
pkginfo = new(goapk.PackageInfo)
if err = i.MapTo(pkginfo); err != nil {
return nil, nil, fmt.Errorf("failed to map .PKGINFO: %v", err)
return nil, nil, nil, fmt.Errorf("failed to map .PKGINFO: %v", err)
}

case fmt.Sprintf("var/lib/db/sbom/%s-%s.spdx.json", pkginfo.Name, pkginfo.Version),
fmt.Sprintf("var/lib/db/sbom/%s-%s-r%d.spdx.json", cfg.Package.Name, cfg.Package.Version, cfg.Package.Epoch):
doc := new(spdx.Document)
if err := json.NewDecoder(io.LimitReader(tr, hdr.Size)).Decode(doc); err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode SBOM: %v", err)
}

for _, p := range doc.Packages {
if strings.HasSuffix(p.Name, ".yaml") {
cfgpkg = &p
break
}
}
if cfgpkg == nil {
return nil, nil, nil, fmt.Errorf("failed to find config package info in SBOM: %v", err)
}

default:
// TODO: Get the SBOM, since we need some info from it too.
continue
}

if cfg != nil && pkginfo != nil {
return cfg, pkginfo, nil
if cfg != nil && pkginfo != nil && cfgpkg != nil {
return cfg, pkginfo, cfgpkg, nil
}
}
// unreachable
Expand Down

0 comments on commit da65599

Please sign in to comment.