Skip to content

Commit

Permalink
anago/stage: Generate the staging provenance metadata
Browse files Browse the repository at this point in the history
anago/stage will now generate the provenance metadata of the artifacts produced
after building. While staging the files, an in-toto attestation is built and
uploaded with the release.

Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
  • Loading branch information
puerco committed Oct 3, 2021
1 parent df6cf9a commit 962663f
Showing 1 changed file with 143 additions and 1 deletion.
144 changes: 143 additions & 1 deletion pkg/anago/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,23 @@ limitations under the License.
package anago

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

intoto "github.com/in-toto/in-toto-golang/in_toto"
"k8s.io/release/pkg/build"
"k8s.io/release/pkg/changelog"
"k8s.io/release/pkg/gcp/gcb"
"k8s.io/release/pkg/provenance"
"k8s.io/release/pkg/release"
"k8s.io/release/pkg/spdx"
"sigs.k8s.io/release-sdk/git"
Expand Down Expand Up @@ -167,6 +172,8 @@ type stageImpl interface {
AddBinariesToSBOM(*spdx.Document, string) error
AddTarfilesToSBOM(*spdx.Document, string) error
VerifyArtifacts([]string) error
GenerateAttestation(*StageState, *StageOptions) (*provenance.Statement, error)
PushAttestation(*provenance.Statement, *StageOptions) error
}

func (d *defaultStageImpl) Submit(options *gcb.Options) error {
Expand Down Expand Up @@ -745,6 +752,12 @@ func (d *DefaultStage) GenerateBillOfMaterials() error {
}

func (d *DefaultStage) StageArtifacts() error {
// Generat the intoto attestation, reloaded with the current run data
statement, err := d.impl.GenerateAttestation(d.state, d.options)
if err != nil {
return errors.Wrap(err, "generating the provenance attestation")
}
// Init a the push options we will use
pushBuildOptions := &build.Options{
Bucket: d.options.Bucket(),
Registry: d.options.ContainerRegistry(),
Expand All @@ -764,6 +777,11 @@ func (d *DefaultStage) StageArtifacts() error {
return errors.Wrap(err, "staging local source tree")
}

// Add the sources tarball to the attestation
if err := statement.AddSubjectFromFile(filepath.Join(workspaceDir, release.SourcesTar)); err != nil {
return errors.Wrap(err, "adding sources tarball to provenance attestation")
}

for _, version := range d.state.versions.Ordered() {
logrus.Infof("Staging artifacts for version %s", version)
buildDir := filepath.Join(
Expand All @@ -778,7 +796,7 @@ func (d *DefaultStage) StageArtifacts() error {
return errors.Wrap(err, "staging local artifacts")
}
gcsPath := filepath.Join(
d.options.Bucket(), "stage", d.options.BuildVersion, version,
d.options.Bucket(), release.StagePath, d.options.BuildVersion, version,
)

// Push gcs-stage to GCS
Expand All @@ -790,6 +808,13 @@ func (d *DefaultStage) StageArtifacts() error {
return errors.Wrap(err, "pushing release artifacts")
}

// Add gcs-stage artifacts to the attestation
if err := statement.ReadSubjectsFromDir(
filepath.Join(buildDir, release.GCSStagePath, version),
); err != nil {
return errors.Wrapf(err, "adding provenance of gcs-stage artifacts for version %s", version)
}

// Push container release-images to GCS
if err := d.impl.PushReleaseArtifacts(
pushBuildOptions,
Expand All @@ -803,6 +828,18 @@ func (d *DefaultStage) StageArtifacts() error {
if err := d.impl.PushContainerImages(pushBuildOptions); err != nil {
return errors.Wrap(err, "pushing container images")
}

// Add gcs-stage artifacts to the attestation
if err := statement.ReadSubjectsFromDir(
filepath.Join(buildDir, release.GCSStagePath, version),
); err != nil {
return errors.Wrapf(err, "adding provenance of release-images for version %s", version)
}
}

// Push the attestation metadata file to the bucket
if err := d.impl.PushAttestation(statement, d.options); err != nil {
return errors.Wrap(err, "writing provenance metadata to disk")
}

// Delete the local source tarball
Expand All @@ -827,3 +864,108 @@ func (d *DefaultStage) StageArtifacts() error {
)
return nil
}

// GenerateAttestation creates a provenance attestation with its predicate
// preloaded with the current krel run information
func (d *defaultStageImpl) GenerateAttestation(state *StageState, options *StageOptions) (attestation *provenance.Statement, err error) {
// Build the arguments RawMessage:
arguments := map[string]string{
"--type=": options.ReleaseType,
"--branch=": options.ReleaseBranch,
"--build-version=": options.BuildVersion,
}
if options.NoMock {
arguments["--nomock"] = "true"
}
argsJSON, err := json.Marshal(arguments)
if err != nil {
return nil, errors.Wrap(err, "marshalling build arguments into json")
}

// Fetch the last commit:
repo, err := git.OpenRepo(gitRoot)
if err != nil {
return nil, errors.Wrap(err, "opening repository to check commit hash")
}
// TODO: When this PR merges and the commit is part of a release:
// https://github.com/kubernetes-sigs/release-sdk/pull/6
// and k/release is bumped, replace the commit logic with this line:
// commitSHA, err := repo.LastCommitSha()
logData, err := repo.ShowLastCommit()
if err != nil {
return nil, errors.Wrap(err, "getting last commit data")
}
re := regexp.MustCompile(`commit\s+([a-f0-9]{40})`)
commitSHA := re.FindString(logData)
if commitSHA == "" {
return nil, errors.New("Unable to find last commit sha in git output")
}

// Create the predicate to populate it with the current
// run metadata:
p := provenance.NewSLSAPredicate()

// TODO: In regular runs, this will insert "master", we should
// record the git sha of the commit in k/release we are using.
p.Builder.ID = fmt.Sprintf(
"pkg:github/%s/%s@%s", os.Getenv("TOOL_ORG"),
os.Getenv("TOOL_REPO"), os.Getenv("TOOL_REF"),
)
// Some of these fields have yet to be checked to assign the
// correct values to them
// This is commented as the in-toto go port does not have it
// p.Metadata.BuildInvocationID: os.Getenv("BUILD_ID"),
p.Metadata.Completeness.Arguments = true // The arguments are complete as we know the from GCB
p.Metadata.Completeness.Materials = true // The materials are complete as we only use the github repo
startTime := state.startTime.UTC()
endTime := time.Now().UTC()
p.Metadata.BuildStartedOn = &startTime
p.Metadata.BuildFinishedOn = &endTime

p.Recipe.Type = "https://cloudbuild.googleapis.com/CloudBuildYaml@v1"
p.Recipe.EntryPoint = "https://github.com/kubernetes/release/blob/master/gcb/stage/cloudbuild.yaml"
p.Recipe.Arguments = argsJSON

p.AddMaterial("git+https://github.com/kubernetes/kubernetes", intoto.DigestSet{"sha1": commitSHA})

// Create the new attestaion and attach the predicate
attestation = provenance.NewSLSAStatement()
attestation.Predicate = p

return attestation, nil
}

// PushAttestation writes the provenance metadata to the staging location in
// the Google Cloud Bucket.
func (d *defaultStageImpl) PushAttestation(attestation *provenance.Statement, options *StageOptions) (err error) {
// Create a temporary file:
f, err := os.CreateTemp("", "provenance-")
if err != nil {
return errors.Wrap(err, "creating temp file for provenance metadata")
}
// Write the provenance statement to disk:
if err := attestation.Write(f.Name()); err != nil {
return errors.Wrap(err, "writing provenance attestation to disk")
}

// TODO for SLSA2: Sign the attestation
// Upload the metadata file to the staging bucket
gcsPath := filepath.Join(
options.Bucket(), release.StagePath, options.BuildVersion, release.ProvenanceFilename,
)

pushBuildOptions := &build.Options{
Bucket: options.Bucket(),
AllowDup: true,
}

if err := d.CheckReleaseBucket(pushBuildOptions); err != nil {
return errors.Wrap(err, "check release bucket access")
}

// Push the provenance file to GCS
return errors.Wrap(
d.PushReleaseArtifacts(pushBuildOptions, f.Name(), filepath.Join(gcsPath)),
"pushing provenance manifest",
)
}

0 comments on commit 962663f

Please sign in to comment.