github.com/yogeshkumararora/slsa-github-generator@v1.10.1-0.20240520161934-11278bd5afb4/internal/builders/go/pkg/provenance.go (about)

     1  // Copyright 2022 SLSA Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package pkg
    16  
    17  import (
    18  	"context"
    19  	"encoding/hex"
    20  	"fmt"
    21  	"os"
    22  
    23  	"github.com/yogeshkumararora/slsa-github-generator/signing"
    24  
    25  	intoto "github.com/in-toto/in-toto-golang/in_toto"
    26  	slsacommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
    27  	"github.com/yogeshkumararora/slsa-github-generator/github"
    28  	"github.com/yogeshkumararora/slsa-github-generator/internal/utils"
    29  	"github.com/yogeshkumararora/slsa-github-generator/slsa"
    30  )
    31  
    32  const (
    33  	buildConfigVersion int = 1
    34  	buildType              = "https://github.com/yogeshkumararora/slsa-github-generator/go@v1"
    35  )
    36  
    37  type (
    38  	step struct {
    39  		WorkingDir string   `json:"workingDir"`
    40  		Command    []string `json:"command"`
    41  		Env        []string `json:"env"`
    42  	}
    43  	buildConfig struct {
    44  		Steps   []step `json:"steps"`
    45  		Version int    `json:"version"`
    46  	}
    47  )
    48  
    49  type goProvenanceBuild struct {
    50  	*slsa.GithubActionsBuild
    51  	buildConfig buildConfig
    52  }
    53  
    54  // URI implements BuildType.URI.
    55  func (b *goProvenanceBuild) URI() string {
    56  	return buildType
    57  }
    58  
    59  // BuildConfig implements BuildType.BuildConfig.
    60  func (b *goProvenanceBuild) BuildConfig(context.Context) (interface{}, error) {
    61  	return b.buildConfig, nil
    62  }
    63  
    64  // GenerateProvenance translates github context into a SLSA provenance
    65  // attestation.
    66  // Spec: https://slsa.dev/provenance/v0.2
    67  func GenerateProvenance(name, digest, command, envs, workingDir string,
    68  	s signing.Signer, r signing.TransparencyLog, provider slsa.ClientProvider,
    69  ) ([]byte, error) {
    70  	gh, err := github.GetWorkflowContext()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	if _, err := hex.DecodeString(digest); err != nil || len(digest) != 64 {
    76  		return nil, fmt.Errorf("sha256 digest is not valid: %s", digest)
    77  	}
    78  
    79  	com, err := utils.UnmarshalList(command)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	env, err := utils.UnmarshalList(envs)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	var cmd []string
    90  	if len(com) > 0 {
    91  		cmd = []string{com[0], "mod", "vendor"}
    92  	}
    93  
    94  	b := goProvenanceBuild{
    95  		GithubActionsBuild: slsa.NewGithubActionsBuild([]intoto.Subject{
    96  			{
    97  				Name: name,
    98  				Digest: slsacommon.DigestSet{
    99  					"sha256": digest,
   100  				},
   101  			},
   102  		}, &gh),
   103  		buildConfig: buildConfig{
   104  			Version: buildConfigVersion,
   105  			Steps: []step{
   106  				// Vendoring step.
   107  				{
   108  					// Note: vendoring and compilation are
   109  					// performed in the same VM, so the compiler is
   110  					// the same.
   111  					Command:    cmd,
   112  					WorkingDir: workingDir,
   113  					// Note: No user-defined env set for this step.
   114  				},
   115  				// Compilation step.
   116  				{
   117  					Command:    com,
   118  					Env:        env,
   119  					WorkingDir: workingDir,
   120  				},
   121  			},
   122  		},
   123  	}
   124  
   125  	// Pre-submit tests don't have access to write OIDC token.
   126  	if provider != nil {
   127  		b.WithClients(provider)
   128  	} else if utils.IsPresubmitTests() {
   129  		// TODO(github.com/yogeshkumararora/slsa-github-generator/issues/124): Remove
   130  		b.GithubActionsBuild.WithClients(&slsa.NilClientProvider{})
   131  	}
   132  
   133  	ctx := context.Background()
   134  	g := slsa.NewHostedActionsGenerator(&b)
   135  	// Pre-submit tests don't have access to write OIDC token.
   136  	if provider != nil {
   137  		g.WithClients(provider)
   138  	} else if utils.IsPresubmitTests() {
   139  		// TODO(github.com/yogeshkumararora/slsa-github-generator/issues/124): Remove
   140  		g.WithClients(&slsa.NilClientProvider{})
   141  	}
   142  	p, err := g.Generate(ctx)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	// Set the architecture based on the runner. Architecture should be the
   148  	// same for the provenance step where this is run and the build step if the
   149  	// reusable workflow is used.
   150  	//
   151  	// NOTE: map is a reference so modifying invEnv modifies
   152  	// p.Predicate.Invocation.Environment.
   153  	invEnv, ok := p.Predicate.Invocation.Environment.(map[string]interface{})
   154  	if !ok {
   155  		panic(fmt.Sprintf("converting %T to map[string]interface{}", p.Predicate.Invocation.Environment))
   156  	}
   157  	invEnv["arch"] = os.Getenv("RUNNER_ARCH")
   158  	invEnv["os"] = os.Getenv("ImageOS")
   159  
   160  	// Add details about the runner's OS to the materials
   161  	runnerMaterials := slsacommon.ProvenanceMaterial{
   162  		// TODO: capture the digest here too
   163  		URI: fmt.Sprintf(
   164  			"https://github.com/actions/virtual-environments/releases/tag/%s/%s",
   165  			os.Getenv("ImageOS"), os.Getenv("ImageVersion"),
   166  		),
   167  	}
   168  	p.Predicate.Materials = append(p.Predicate.Materials, runnerMaterials)
   169  
   170  	if utils.IsPresubmitTests() {
   171  		fmt.Println("Pre-submit tests detected. Skipping signing.")
   172  		return utils.MarshalToBytes(*p)
   173  	}
   174  
   175  	// Sign the provenance.
   176  	att, err := s.Sign(ctx, &intoto.Statement{
   177  		StatementHeader: p.StatementHeader,
   178  		Predicate:       p.Predicate,
   179  	})
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	// Upload the signed attestation to rekor.
   185  	logEntry, err := r.Upload(ctx, att)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	fmt.Printf("Uploaded signed attestation to rekor with UUID %s.\n", logEntry.UUID())
   191  
   192  	return att.Bytes(), nil
   193  }