Skip to content

Commit

Permalink
Merge pull request #2300 from puerco/final-provenance
Browse files Browse the repository at this point in the history
Provenance attesattion for artifacts
  • Loading branch information
k8s-ci-robot authored Nov 15, 2021
2 parents 365489a + a4f6884 commit 58b4fff
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 18 deletions.
2 changes: 1 addition & 1 deletion pkg/anago/anago.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ func (r *Release) Run() error {
return errors.Wrap(err, "init log file")
}

logger := log.NewStepLogger(10)
logger := log.NewStepLogger(11)
logger.Infof("Using krel version:\n%s", version.Get().String())

logger.WithStep().Info("Validating options")
Expand Down
18 changes: 10 additions & 8 deletions pkg/anago/anagofakes/fake_release_impl.go

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

29 changes: 25 additions & 4 deletions pkg/anago/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ type releaseImpl interface {
gcsIndexRootPath, gcsReleaseNotesPath, version string,
) error
CreatePubBotBranchIssue(string) error
CheckStageProvenance(string, string) error
CheckStageProvenance(string, string, *release.Versions) error
}

func (d *defaultReleaseImpl) Submit(options *gcb.Options) error {
Expand Down Expand Up @@ -446,6 +446,18 @@ func (d *DefaultRelease) PushArtifacts() error {
return errors.Wrap(err, "copy release notes to bucket")
}

for _, version := range d.state.versions.Ordered() {
if err := d.impl.CopyToRemote(
objStore,
filepath.Join(os.TempDir(), fmt.Sprintf("provenance-%s.json", version)),
gcsReleaseRootPath+fmt.Sprintf(
"/%s/provenance.json", version,
),
); err != nil {
return errors.Wrap(err, "copying provenance data to release bucket")
}
}

logrus.Info("Publishing updated release notes index")
if err := d.impl.PublishReleaseNotesIndex(
gcsReleaseRootPath, gcsReleaseNotesPath, d.state.versions.Prime(),
Expand Down Expand Up @@ -630,14 +642,23 @@ func (d *DefaultRelease) Archive() error {
// CheckProvenance verifies the artifacts staged in the release bucket
// by verifying the provenance metadata generated during the stage run.
func (d *DefaultRelease) CheckProvenance() error {
return d.impl.CheckStageProvenance(d.options.Bucket(), d.options.BuildVersion)
return d.impl.CheckStageProvenance(d.options.Bucket(), d.options.BuildVersion, d.state.versions)
}

func (d *defaultReleaseImpl) CheckStageProvenance(bucket, buildVersion string) error {
func (d *defaultReleaseImpl) CheckStageProvenance(bucket, buildVersion string, versions *release.Versions) error {
checker := release.NewProvenanceChecker(&release.ProvenanceCheckerOptions{
ScratchDirectory: filepath.Join(workspaceDir, "provenance-workdir"),
StageBucket: bucket,
})

return checker.CheckStageProvenance(buildVersion)
if err := checker.CheckStageProvenance(buildVersion); err != nil {
return errors.Wrap(err, "checking provenance of staged artifacts")
}

// Write the final, end-user attestations
if err := checker.GenerateFinalAttestation(buildVersion, versions); err != nil {
return errors.Wrap(err, "generating final SLSA attestations")
}

return nil
}
3 changes: 3 additions & 0 deletions pkg/anago/release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ func TestCheckProvenance(t *testing.T) {
} {
opts := anago.DefaultReleaseOptions()
sut := anago.NewDefaultRelease(opts)
sut.SetState(
generateTestingReleaseState(&testStateParameters{versionsTag: &testVersionTag}),
)
mock := &anagofakes.FakeReleaseImpl{}
tc.prepare(mock)
sut.SetImpl(mock)
Expand Down
95 changes: 95 additions & 0 deletions pkg/provenance/provenancefakes/fake_statement_implementation.go

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

21 changes: 17 additions & 4 deletions pkg/provenance/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func (s *Statement) Write(path string) error {
return s.impl.Write(s, path)
}

func (s *Statement) ToJSON() ([]byte, error) {
return s.impl.ToJSON(s)
}

// ReadSubjectsFromDir reads a directory and adds every file as a subject
// to the statement.
func (s *Statement) ReadSubjectsFromDir(path string) (err error) {
Expand Down Expand Up @@ -106,6 +110,7 @@ type StatementImplementation interface {
ReadSubjectsFromDir(*Statement, string) error
SubjectFromFile(string) (intoto.Subject, error)
Write(*Statement, string) error
ToJSON(s *Statement) ([]byte, error)
ClonePredicate(*Statement, string) error
VerifySubjects(path string, subjects *[]intoto.Subject) (err error)
}
Expand Down Expand Up @@ -174,20 +179,28 @@ func (si *defaultStatementImplementation) SubjectFromFile(filePath string) (subj
return subject, nil
}

func (si *defaultStatementImplementation) ToJSON(s *Statement) ([]byte, error) {
jsonData, err := json.Marshal(s)
if err != nil {
return nil, errors.Wrap(err, "marshalling statement to json")
}
return jsonData, nil
}

// Write dumps the statement data to disk in json
func (si *defaultStatementImplementation) Write(s *Statement, path string) error {
jsonData, err := json.Marshal(s)
jsonData, err := si.ToJSON(s)
if err != nil {
return errors.Wrap(err, "marshalling statement to json")
return err
}
return errors.Wrap(
os.WriteFile(path, jsonData, os.FileMode(0o644)),
"writing predicate file",
)
}

// ClonePredicate clonea the predicate from the file in manifestPath
// to Statement s
// ClonePredicate clones the predicate from the file in manifestPath
// to into the Statement
func (si *defaultStatementImplementation) ClonePredicate(s *Statement, manifestPath string) error {
otherStatment, err := LoadStatement(manifestPath)
if err != nil {
Expand Down
49 changes: 49 additions & 0 deletions pkg/release/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/release/pkg/provenance"
"k8s.io/release/pkg/spdx"
"sigs.k8s.io/release-sdk/object"
"sigs.k8s.io/release-utils/util"
)
Expand Down Expand Up @@ -90,6 +91,24 @@ func (pc *ProvenanceChecker) CheckStageProvenance(buildVersion string) error {
return nil
}

// GenerateFinalAttestation combines the stage provenance attestation
// with a release sbom to create the end-user provenance atteatation
func (pc *ProvenanceChecker) GenerateFinalAttestation(buildVersion string, versions *Versions) error {
statementPath := filepath.Join(pc.options.StageDirectory, buildVersion, ProvenanceFilename)
for _, version := range versions.Ordered() {
if err := pc.impl.generateFinalAttestation(
pc.options,
filepath.Join(
pc.options.StageDirectory, buildVersion, version, GCSStagePath, version, "kubernetes-release.spdx",
),
statementPath, version,
); err != nil {
return errors.Wrapf(err, "generating provenance data for %s", version)
}
}
return nil
}

type ProvenanceCheckerOptions struct {
StageBucket string // Bucket where the artifacts are stored
StageDirectory string // Directory where artifacts will be downloaded
Expand All @@ -100,6 +119,7 @@ type provenanceCheckerImplementation interface {
downloadStagedArtifacts(*ProvenanceCheckerOptions, *object.GCS, string) error
processAttestation(*ProvenanceCheckerOptions, string) (*provenance.Statement, error)
checkProvenance(*ProvenanceCheckerOptions, *provenance.Statement) error
generateFinalAttestation(opts *ProvenanceCheckerOptions, sbom, stageProvenance, version string) error
}

type defaultProvenanceCheckerImpl struct{}
Expand Down Expand Up @@ -151,6 +171,35 @@ func (di *defaultProvenanceCheckerImpl) checkProvenance(
return errors.Wrap(s.VerifySubjects(opts.StageDirectory), "checking subjects in attestation")
}

func (di *defaultProvenanceCheckerImpl) generateFinalAttestation(
opts *ProvenanceCheckerOptions, sbom, stageProvenance, version string) error {
doc, err := spdx.OpenDoc(sbom)
if err != nil {
return errors.Wrapf(err, "parsing sbom for version %s from %s", version, sbom)
}

slsaStatement := doc.ToProvenanceStatement(spdx.DefaultProvenanceOptions)

// Rewrite the provenance sublects to list their full paths in the bucket
for i, sub := range slsaStatement.Subject {
slsaStatement.Subject[i].Name = object.GcsPrefix + filepath.Join(
opts.StageBucket, "release", version, sub.Name,
)
}
if err := slsaStatement.ClonePredicate(stageProvenance); err != nil {
return errors.Wrapf(
err, "cloning SLSA predicate from staging provenance: %s", stageProvenance,
)
}
if err := slsaStatement.Write(
filepath.Join(os.TempDir(), fmt.Sprintf("provenance-%s.json", version)),
); err != nil {
return errors.Wrapf(err, "writing final provenance attestation for %s", version)
}

return nil
}

func NewProvenanceReader(opts *ProvenanceReaderOptions) *ProvenanceReader {
return &ProvenanceReader{
options: opts,
Expand Down
Loading

0 comments on commit 58b4fff

Please sign in to comment.