github.com/yogeshkumararora/slsa-github-generator@v1.10.1-0.20240520161934-11278bd5afb4/internal/builders/docker/pkg/builder_test.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 // https://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 "errors" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 intoto "github.com/in-toto/in-toto-golang/in_toto" 27 ) 28 29 func Test_CreateBuildDefinition(t *testing.T) { 30 config := &DockerBuildConfig{ 31 SourceRepo: "git+https://github.com/yogeshkumararora/slsa-github-generator@refs/heads/main", 32 SourceDigest: Digest{Alg: "sha1", Value: "cf5804b5c6f1a4b2a0b03401a487dfdfbe3a5f00"}, 33 BuilderImage: DockerImage{ 34 Name: "bash", 35 Digest: Digest{Alg: "sha256", Value: "9e2ba52487d945504d250de186cb4fe2e3ba023ed2921dd6ac8b97ed43e76af9"}, 36 }, 37 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 38 } 39 40 db := &DockerBuild{ 41 config: config, 42 buildConfig: &BuildConfig{ 43 Command: []string{"cp", "internal/builders/docker/testdata/config.toml", "config.toml"}, 44 ArtifactPath: "config.toml", 45 }, 46 } 47 48 got := db.CreateBuildDefinition() 49 50 want, err := loadBuildDefinitionFromFile("../testdata/build-definition.json") 51 if err != nil { 52 t.Fatalf("%v", err) 53 } 54 55 if diff := cmp.Diff(got, want); diff != "" { 56 t.Errorf(diff) 57 } 58 } 59 60 func Test_GitClient_verifyOrFetchRepo(t *testing.T) { 61 config := &DockerBuildConfig{ 62 // Use a small repo for test 63 SourceRepo: "git+https://github.com/project-oak/transparent-release", 64 // The digest value does not matter for the test 65 SourceDigest: Digest{Alg: "sha1", Value: "does-not-matter"}, 66 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 67 ForceCheckout: false, 68 // BuilderImage field is not relevant, so it is omitted 69 } 70 gc, err := newGitClient(config, 1) 71 if err != nil { 72 t.Fatalf("Could create GitClient: %v", err) 73 } 74 75 // We expect it to fail at verifyCommit 76 if got, want := gc.verifyOrFetchRepo(), errGitCommitMismatch; !errors.Is(got, want) { 77 t.Errorf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors())) 78 } 79 } 80 81 func Test_GitClient_fetchSourcesFromGitRepo(t *testing.T) { 82 // The call to fetchSourcesFromGitRepo will change directory. Here we store 83 // the current working directory, and change back to it when the test ends. 84 cwd, err := os.Getwd() 85 if err != nil { 86 t.Fatalf("couldn't get current working directory: %v", err) 87 } 88 89 config := &DockerBuildConfig{ 90 // Use a small repo for test 91 SourceRepo: "git+https://github.com/project-oak/transparent-release", 92 // The digest value does not matter for the test 93 SourceDigest: Digest{Alg: "sha1", Value: "does-no-matter"}, 94 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 95 ForceCheckout: false, 96 // BuilderImage field is not relevant, so it is omitted 97 } 98 gc, err := newGitClient(config, 1) 99 if err != nil { 100 t.Fatalf("Could not create GitClient: %v", err) 101 } 102 103 // We expect the checkout to fail 104 if got, want := gc.fetchSourcesFromGitRepo(), errGitCheckout; !errors.Is(got, want) { 105 t.Errorf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors())) 106 } 107 108 // Cleanup 109 gc.cleanupAllFiles() 110 // Recover the original test state. 111 if err := os.Chdir(cwd); err != nil { 112 t.Errorf("couldn't change directory to %q: %v", cwd, err) 113 } 114 } 115 116 func Test_GitClient_sourceWithRef(t *testing.T) { 117 // This tests that specifying a source repository with a ref resolves 118 // to a valid GitClient source repository 119 120 config := &DockerBuildConfig{ 121 // Use a small repo for test 122 SourceRepo: "git+https://github.com/project-oak/transparent-release@refs/heads/main", 123 // The digest value does not matter for the test 124 SourceDigest: Digest{Alg: "sha1", Value: "does-no-matter"}, 125 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 126 ForceCheckout: false, 127 // BuilderImage field is not relevant, so it is omitted 128 } 129 gc, err := newGitClient(config, 1) 130 if err != nil { 131 t.Fatalf("Could not create GitClient: %v", err) 132 } 133 134 // We expect that the sourceRef is set to refs/heads/main 135 if gc.sourceRef == nil || *gc.sourceRef != "refs/heads/main" { 136 t.Errorf("expected sourceRef to be refs/heads/main, got %s", *gc.sourceRef) 137 } 138 } 139 140 func Test_GitClient_invalidSourceRef(t *testing.T) { 141 // This tests that specifying a source repository with a ref resolves 142 // to a valid GitClient source repository 143 144 config := &DockerBuildConfig{ 145 // Use a small repo for test 146 SourceRepo: "git+https://github.com/project-oak/transparent-release@refs/heads/main@invalid", 147 // The digest value does not matter for the test 148 SourceDigest: Digest{Alg: "sha1", Value: "does-no-matter"}, 149 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 150 ForceCheckout: false, 151 // BuilderImage field is not relevant, so it is omitted 152 } 153 _, err := newGitClient(config, 1) 154 if err == nil { 155 t.Fatalf("expected error creating GitClient") 156 } 157 if !strings.Contains(err.Error(), "invalid source repository format") { 158 t.Fatalf("expected invalid source ref error creating GitClient, got %s", err) 159 } 160 } 161 162 func Test_inspectArtifacts(t *testing.T) { 163 // Note: If the files in ../testdata/ change, this test must be updated. 164 pattern := "testdata/*" 165 out := t.TempDir() 166 167 wd, err := os.Getwd() 168 if err != nil { 169 t.Fatal(err) 170 } 171 // Execute this function from docker/ instead of pkg/ so that the testdata 172 // folder is in the current dir. 173 t.Cleanup(func() { 174 if err := os.Chdir(wd); err != nil { 175 t.Fatal(err) 176 } 177 }) 178 if err := os.Chdir(filepath.Dir(wd)); err != nil { 179 t.Fatal(err) 180 } 181 182 got, err := inspectAndWriteArtifacts(pattern, out, filepath.Dir(wd)) 183 if err != nil { 184 t.Fatalf("failed to inspect artifacts: %v", err) 185 } 186 187 s1 := intoto.Subject{ 188 Name: "build-definition.json", 189 Digest: map[string]string{"sha256": "ab5582bfb6128c534583e1fea92421158c9de5e72e86c78cf550a8adcbf12db5"}, 190 } 191 s2 := intoto.Subject{ 192 Name: "config.toml", 193 Digest: map[string]string{"sha256": "975a0582b8c9607f3f20a6b8cfef01b25823e68c5c3658e6e1ccaaced2a3255d"}, 194 } 195 196 s3 := intoto.Subject{ 197 Name: "slsa1-provenance.json", 198 Digest: map[string]string{"sha256": "8b43bccfe6704594dcfbd8824097c16f61b79b32ec5439f4704cdf0b4529958b"}, 199 } 200 201 s4 := intoto.Subject{ 202 Name: "wildcard-config.toml", 203 Digest: map[string]string{"sha256": "d9b8670f1b9616db95b0dc84cbc68062c691ef31bb9240d82753de0739c59194"}, 204 } 205 206 want := []intoto.Subject{s1, s2, s3, s4} 207 208 if diff := cmp.Diff(got, want); diff != "" { 209 t.Errorf(diff) 210 } 211 } 212 213 // When running in the checkout of the repository root, root == "". 214 func Test_inspectArtifactsNoRoot(t *testing.T) { 215 // Note: If the files in ../testdata/ change, this test must be updated. 216 pattern := "testdata/*" 217 out := t.TempDir() 218 219 wd, err := os.Getwd() 220 if err != nil { 221 t.Fatal(err) 222 } 223 t.Cleanup(func() { 224 if err := os.Chdir(wd); err != nil { 225 t.Fatal(err) 226 } 227 }) 228 if err := os.Chdir(".."); err != nil { 229 t.Fatal(err) 230 } 231 232 got, err := inspectAndWriteArtifacts(pattern, out, "") 233 if err != nil { 234 t.Fatalf("failed to inspect artifacts: %v", err) 235 } 236 237 s1 := intoto.Subject{ 238 Name: "build-definition.json", 239 Digest: map[string]string{"sha256": "ab5582bfb6128c534583e1fea92421158c9de5e72e86c78cf550a8adcbf12db5"}, 240 } 241 s2 := intoto.Subject{ 242 Name: "config.toml", 243 Digest: map[string]string{"sha256": "975a0582b8c9607f3f20a6b8cfef01b25823e68c5c3658e6e1ccaaced2a3255d"}, 244 } 245 246 s3 := intoto.Subject{ 247 Name: "slsa1-provenance.json", 248 Digest: map[string]string{"sha256": "8b43bccfe6704594dcfbd8824097c16f61b79b32ec5439f4704cdf0b4529958b"}, 249 } 250 251 s4 := intoto.Subject{ 252 Name: "wildcard-config.toml", 253 Digest: map[string]string{"sha256": "d9b8670f1b9616db95b0dc84cbc68062c691ef31bb9240d82753de0739c59194"}, 254 } 255 256 want := []intoto.Subject{s1, s2, s3, s4} 257 258 if diff := cmp.Diff(got, want); diff != "" { 259 t.Errorf(diff) 260 } 261 } 262 263 type testFetcher struct{} 264 265 func (testFetcher) Fetch() (*RepoCheckoutInfo, error) { 266 return &RepoCheckoutInfo{}, nil 267 } 268 269 func Test_Builder_SetUpBuildState(t *testing.T) { 270 wd, err := os.Getwd() 271 if err != nil { 272 t.Fatal(err) 273 } 274 // Execute this function from docker/ instead of pkg/ so that the testdata config 275 // is in the current dir. 276 t.Cleanup(func() { 277 if err := os.Chdir(wd); err != nil { 278 t.Fatal(err) 279 } 280 }) 281 if err := os.Chdir(filepath.Dir(wd)); err != nil { 282 t.Fatal(err) 283 } 284 285 config := DockerBuildConfig{ 286 SourceRepo: "git+https://github.com/project-oak/transparent-release", 287 SourceDigest: Digest{Alg: "sha1", Value: "cf5804b5c6f1a4b2a0b03401a487dfdfbe3a5f00"}, 288 BuilderImage: DockerImage{ 289 Name: "bash", 290 Digest: Digest{Alg: "sha256", Value: "9e2ba52487d945504d250de186cb4fe2e3ba023ed2921dd6ac8b97ed43e76af9"}, 291 }, 292 BuildConfigPath: "testdata/config.toml", 293 } 294 295 f := testFetcher{} 296 b := Builder{ 297 repoFetcher: f, 298 config: config, 299 } 300 301 db, err := b.SetUpBuildState() 302 if err != nil { 303 t.Fatalf("couldn't set up build state: %v", err) 304 } 305 if db == nil { 306 t.Error("db is null") 307 } 308 } 309 310 func Test_ParseProvenance(t *testing.T) { 311 provenance := loadProvenance(t) 312 got := &provenance.Predicate.BuildDefinition 313 314 want, err := loadBuildDefinitionFromFile("../testdata/build-definition.json") 315 if err != nil { 316 t.Fatalf("%v", err) 317 } 318 319 if diff := cmp.Diff(got, want); diff != "" { 320 t.Errorf(diff) 321 } 322 } 323 324 func Test_ProvenanceStatementSLSA1_ToDockerBuildConfig(t *testing.T) { 325 provenance := loadProvenance(t) 326 got, err := provenance.ToDockerBuildConfig(true) 327 if err != nil { 328 t.Fatalf("%v", err) 329 } 330 331 want := &DockerBuildConfig{ 332 SourceRepo: "git+https://github.com/yogeshkumararora/slsa-github-generator@refs/heads/main", 333 SourceDigest: Digest{ 334 Alg: "sha1", 335 Value: "cf5804b5c6f1a4b2a0b03401a487dfdfbe3a5f00", 336 }, 337 BuilderImage: DockerImage{ 338 Name: "bash", 339 Digest: Digest{ 340 Alg: "sha256", 341 Value: "9e2ba52487d945504d250de186cb4fe2e3ba023ed2921dd6ac8b97ed43e76af9", 342 }, 343 }, 344 BuildConfigPath: "internal/builders/docker/testdata/config.toml", 345 ForceCheckout: true, 346 } 347 if diff := cmp.Diff(got, want); diff != "" { 348 t.Errorf(diff) 349 } 350 } 351 352 func loadProvenance(t *testing.T) ProvenanceStatementSLSA1 { 353 bytes, err := os.ReadFile("../testdata/slsa1-provenance.json") 354 if err != nil { 355 t.Fatalf("Reading the provenance file: %v", err) 356 } 357 358 provenance, err := ParseProvenance(bytes) 359 if err != nil { 360 t.Fatalf("Parsing the provenance file: %v", err) 361 } 362 return *provenance 363 }