Commit 9772fc9

Eric Bower  ·  2026-05-08 15:14:26 -0400 EDT
parent ab5ea95
feat: attestation
1 files changed,  +53, -4
+53, -4
......@@ -17,6 +17,7 @@ import (
1717 "os/exec"
1818 "os/signal"
1919 "path/filepath"
20+ "runtime"
2021 "strconv"
2122 "strings"
2223 "syscall"
......@@ -313,6 +314,9 @@ type Workspace interface {
313314 Setup() error
314315 Cleanup() error
315316 GetDir() string
317+ // Checksum returns a content hash of the workspace (e.g. sha256 of the tarball).
318+ // Returns empty string if not available.
319+ Checksum() string
316320 }
317321
318322 type WorkspaceRsync struct {
......@@ -355,11 +359,16 @@ func (w *WorkspaceRsync) GetDir() string {
355359 return w.Dest
356360 }
357361
362+func (w *WorkspaceRsync) Checksum() string {
363+ return ""
364+}
365+
358366 type WorkspaceTar struct {
359- Cfg *Cfg
360- Logger *slog.Logger
361- Source string // e.g. "pgs.sh:/private-ci/workspaces/repo_abc123.tar"
362- Dest string
367+ Cfg *Cfg
368+ Logger *slog.Logger
369+ Source string // e.g. "pgs.sh:/private-ci/workspaces/repo_abc123.tar"
370+ Dest string
371+ checksum string
363372 }
364373
365374 func (w *WorkspaceTar) Setup() error {
......@@ -383,6 +392,14 @@ func (w *WorkspaceTar) Setup() error {
383392 return fmt.Errorf("rsync download: %w", err)
384393 }
385394
395+ // Compute sha256 checksum of the tarball
396+ tarData, err := os.ReadFile(tarPath)
397+ if err != nil {
398+ return fmt.Errorf("read tar for checksum: %w", err)
399+ }
400+ w.checksum = fmt.Sprintf("sha256:%x", sha256.Sum256(tarData))
401+ log.Debug("workspace checksum", "checksum", w.checksum)
402+
386403 // Open the tar file for extraction
387404 f, err := os.Open(tarPath)
388405 if err != nil {
......@@ -436,6 +453,10 @@ func (w *WorkspaceTar) Setup() error {
436453 return nil
437454 }
438455
456+func (w *WorkspaceTar) Checksum() string {
457+ return w.checksum
458+}
459+
439460 func (w *WorkspaceTar) Cleanup() error {
440461 return nil
441462 }
......@@ -586,6 +607,26 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
586607 }
587608 }
588609
610+ // Write attestation.json with runner/workspace provenance
611+ hostname, _ := os.Hostname()
612+ attestation := map[string]interface{}{
613+ "runner": map[string]string{
614+ "hostname": hostname,
615+ "os": runtimeOS(),
616+ "arch": runtimeArch(),
617+ },
618+ "provenance": map[string]string{
619+ "repo": eventData.Name,
620+ "branch": eventData.Branch,
621+ "commit": eventData.Commit,
622+ },
623+ "workspace_checksum": eng.Wk.Checksum(),
624+ }
625+ attestationBytes, _ := json.Marshal(attestation)
626+ if err := os.WriteFile(filepath.Join(eventDir, "attestation.json"), attestationBytes, 0644); err != nil {
627+ log.Error("write attestation", "err", err)
628+ }
629+
589630 manifest, err := eng.FindManifest()
590631 if err != nil {
591632 fmt.Fprintf(os.Stdout, "❌ %s\n\n", err) //nolint:errcheck
......@@ -1293,6 +1334,14 @@ func generateJobID(name, workspace string) string {
12931334 return jobIDFor(name, workspace, time.Now().UnixNano())
12941335 }
12951336
1337+func runtimeOS() string {
1338+ return runtime.GOOS
1339+}
1340+
1341+func runtimeArch() string {
1342+ return runtime.GOARCH
1343+}
1344+
12961345 func jobIDFor(name, workspace string, ts int64) string {
12971346 h := sha256.Sum256([]byte(name + workspace + fmt.Sprintf("%d", ts)))
12981347 return fmt.Sprintf("%x", h[:4])