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 }