github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/validate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package gadget_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/gadget" 31 ) 32 33 type validateGadgetTestSuite struct { 34 dir string 35 } 36 37 var _ = Suite(&validateGadgetTestSuite{}) 38 39 func (s *validateGadgetTestSuite) SetUpTest(c *C) { 40 s.dir = c.MkDir() 41 } 42 43 func (s *validateGadgetTestSuite) TestRuleValidateStructureReservedLabels(c *C) { 44 for _, tc := range []struct { 45 role, label, err string 46 }{ 47 {label: "ubuntu-seed", err: `label "ubuntu-seed" is reserved`}, 48 // 2020-12-02: disable for customer hotfix 49 /*{label: "ubuntu-boot", err: `label "ubuntu-boot" is reserved`},*/ 50 {label: "ubuntu-data", err: `label "ubuntu-data" is reserved`}, 51 {label: "ubuntu-save", err: `label "ubuntu-save" is reserved`}, 52 // these are ok 53 {role: "system-boot", label: "ubuntu-boot"}, 54 {label: "random-ubuntu-label"}, 55 } { 56 err := gadget.RuleValidateVolumeStructure(&gadget.VolumeStructure{ 57 Type: "21686148-6449-6E6F-744E-656564454649", 58 Role: tc.role, 59 Filesystem: "ext4", 60 Label: tc.label, 61 Size: 10 * 1024, 62 }) 63 if tc.err == "" { 64 c.Check(err, IsNil) 65 } else { 66 c.Check(err, ErrorMatches, tc.err) 67 } 68 } 69 70 } 71 72 func (s *validateGadgetTestSuite) TestEnsureVolumeRuleConsistency(c *C) { 73 state := func(seed bool, label string) *gadget.ValidationState { 74 systemDataVolume := &gadget.VolumeStructure{Label: label} 75 systemSeedVolume := (*gadget.VolumeStructure)(nil) 76 if seed { 77 systemSeedVolume = &gadget.VolumeStructure{} 78 } 79 return &gadget.ValidationState{ 80 SystemSeed: systemSeedVolume, 81 SystemData: systemDataVolume, 82 } 83 } 84 85 for i, tc := range []struct { 86 s *gadget.ValidationState 87 err string 88 }{ 89 90 // we have the system-seed role 91 {state(true, ""), ""}, 92 {state(true, "foobar"), `.* must have an implicit label or "ubuntu-data", not "foobar"`}, 93 {state(true, "writable"), `.* must have an implicit label or "ubuntu-data", not "writable"`}, 94 {state(true, "ubuntu-data"), ""}, 95 96 // we don't have the system-seed role (old systems) 97 {state(false, ""), ""}, // implicit is ok 98 {state(false, "foobar"), `.* must have an implicit label or "writable", not "foobar"`}, 99 {state(false, "writable"), ""}, 100 {state(false, "ubuntu-data"), `.* must have an implicit label or "writable", not "ubuntu-data"`}, 101 } { 102 c.Logf("tc: %v %p %v", i, tc.s.SystemSeed, tc.s.SystemData.Label) 103 104 err := gadget.EnsureVolumeRuleConsistency(tc.s, nil) 105 if tc.err != "" { 106 c.Check(err, ErrorMatches, tc.err) 107 } else { 108 c.Check(err, IsNil) 109 } 110 } 111 112 // Check system-seed label 113 for i, tc := range []struct { 114 l string 115 err string 116 }{ 117 {"", ""}, 118 {"foobar", `system-seed structure must have an implicit label or "ubuntu-seed", not "foobar"`}, 119 {"ubuntu-seed", ""}, 120 } { 121 c.Logf("tc: %v %v", i, tc.l) 122 s := state(true, "") 123 s.SystemSeed.Label = tc.l 124 err := gadget.EnsureVolumeRuleConsistency(s, nil) 125 if tc.err != "" { 126 c.Check(err, ErrorMatches, tc.err) 127 } else { 128 c.Check(err, IsNil) 129 } 130 } 131 132 // Check system-seed without system-data 133 vs := &gadget.ValidationState{} 134 err := gadget.EnsureVolumeRuleConsistency(vs, nil) 135 c.Assert(err, IsNil) 136 vs.SystemSeed = &gadget.VolumeStructure{} 137 err = gadget.EnsureVolumeRuleConsistency(vs, nil) 138 c.Assert(err, ErrorMatches, "the system-seed role requires system-data to be defined") 139 140 // Check system-save 141 vsWithSave := &gadget.ValidationState{ 142 SystemData: &gadget.VolumeStructure{}, 143 SystemSeed: &gadget.VolumeStructure{}, 144 SystemSave: &gadget.VolumeStructure{}, 145 } 146 err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil) 147 c.Assert(err, IsNil) 148 // use illegal label on system-save 149 vsWithSave.SystemSave.Label = "foo" 150 err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil) 151 c.Assert(err, ErrorMatches, `system-save structure must have an implicit label or "ubuntu-save", not "foo"`) 152 // complains when either system-seed or system-data is missing 153 vsWithSave.SystemSeed = nil 154 err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil) 155 c.Assert(err, ErrorMatches, "system-save requires system-seed and system-data structures") 156 vsWithSave.SystemData = nil 157 err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil) 158 c.Assert(err, ErrorMatches, "system-save requires system-seed and system-data structures") 159 } 160 161 func (s *validateGadgetTestSuite) TestValidateConsistencyWithoutConstraints(c *C) { 162 for i, tc := range []struct { 163 role string 164 label string 165 err string 166 }{ 167 // when constraints are nil, the system-seed role and ubuntu-data label on the 168 // system-data structure should be consistent 169 {"system-seed", "", ""}, 170 {"system-seed", "writable", `.* must have an implicit label or "ubuntu-data", not "writable"`}, 171 {"system-seed", "ubuntu-data", ""}, 172 {"", "", ""}, 173 {"", "writable", ""}, 174 {"", "ubuntu-data", `.* must have an implicit label or "writable", not "ubuntu-data"`}, 175 } { 176 c.Logf("tc: %v %v %v", i, tc.role, tc.label) 177 b := &bytes.Buffer{} 178 179 fmt.Fprintf(b, ` 180 volumes: 181 pc: 182 bootloader: grub 183 schema: mbr 184 structure:`) 185 186 if tc.role == "system-seed" { 187 fmt.Fprintf(b, ` 188 - name: Recovery 189 size: 10M 190 type: 83 191 role: system-seed`) 192 } 193 194 fmt.Fprintf(b, ` 195 - name: Data 196 size: 10M 197 type: 83 198 role: system-data 199 filesystem-label: %s`, tc.label) 200 201 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes()) 202 ginfo, err := gadget.ReadInfo(s.dir, nil) 203 c.Assert(err, IsNil) 204 err = gadget.Validate(ginfo, nil, nil) 205 if tc.err != "" { 206 c.Check(err, ErrorMatches, tc.err) 207 } else { 208 c.Check(err, IsNil) 209 } 210 } 211 } 212 213 func (s *validateGadgetTestSuite) TestValidateConsistencyWithConstraints(c *C) { 214 bloader := ` 215 volumes: 216 pc: 217 bootloader: grub 218 schema: mbr 219 structure:` 220 221 for i, tc := range []struct { 222 addSeed bool 223 dataLabel string 224 requireSeed bool 225 addSave bool 226 saveLabel string 227 err string 228 }{ 229 // when constraints are nil, the system-seed role and ubuntu-data label on the 230 // system-data structure should be consistent 231 {addSeed: true, requireSeed: true}, 232 {addSeed: true, err: `.* model does not support the system-seed role`}, 233 {addSeed: true, dataLabel: "writable", requireSeed: true, 234 err: `.* system-data structure must have an implicit label or "ubuntu-data", not "writable"`}, 235 {addSeed: true, dataLabel: "writable", 236 err: `.* model does not support the system-seed role`}, 237 {addSeed: true, dataLabel: "ubuntu-data", requireSeed: true}, 238 {addSeed: true, dataLabel: "ubuntu-data", 239 err: `.* model does not support the system-seed role`}, 240 {dataLabel: "writable", requireSeed: true, 241 err: `.* model requires system-seed structure, but none was found`}, 242 {dataLabel: "writable"}, 243 {dataLabel: "ubuntu-data", requireSeed: true, 244 err: `.* model requires system-seed structure, but none was found`}, 245 {dataLabel: "ubuntu-data", err: `.* system-data structure must have an implicit label or "writable", not "ubuntu-data"`}, 246 {addSave: true, err: `.* system-save requires system-seed and system-data structures`}, 247 {addSeed: true, requireSeed: true, addSave: true, saveLabel: "foo", 248 err: `.* system-save structure must have an implicit label or "ubuntu-save", not "foo"`}, 249 } { 250 c.Logf("tc: %v %v %v %v", i, tc.addSeed, tc.dataLabel, tc.requireSeed) 251 b := &bytes.Buffer{} 252 253 fmt.Fprintf(b, bloader) 254 if tc.addSeed { 255 fmt.Fprintf(b, ` 256 - name: Recovery 257 size: 10M 258 type: 83 259 role: system-seed`) 260 } 261 262 fmt.Fprintf(b, ` 263 - name: Data 264 size: 10M 265 type: 83 266 role: system-data 267 filesystem-label: %s`, tc.dataLabel) 268 if tc.addSave { 269 fmt.Fprintf(b, ` 270 - name: Save 271 size: 10M 272 type: 83 273 role: system-save`) 274 if tc.saveLabel != "" { 275 fmt.Fprintf(b, ` 276 filesystem-label: %s`, tc.saveLabel) 277 278 } 279 } 280 281 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes()) 282 283 constraints := &modelConstraints{ 284 classic: false, 285 systemSeed: tc.requireSeed, 286 } 287 288 ginfo, err := gadget.ReadInfo(s.dir, constraints) 289 c.Assert(err, IsNil) 290 err = gadget.Validate(ginfo, constraints, nil) 291 if tc.err != "" { 292 c.Check(err, ErrorMatches, tc.err) 293 } else { 294 c.Check(err, IsNil) 295 } 296 } 297 298 // test error with no volumes 299 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(bloader)) 300 301 constraints := &modelConstraints{ 302 systemSeed: true, 303 } 304 ginfo, err := gadget.ReadInfo(s.dir, constraints) 305 c.Assert(err, IsNil) 306 err = gadget.Validate(ginfo, constraints, nil) 307 c.Assert(err, ErrorMatches, ".*: model requires system-seed partition, but no system-seed or system-data partition found") 308 } 309 310 func (s *validateGadgetTestSuite) TestValidateRoleDuplicated(c *C) { 311 312 for _, role := range []string{"system-seed", "system-data", "system-boot"} { 313 gadgetYamlContent := fmt.Sprintf(` 314 volumes: 315 pc: 316 bootloader: grub 317 structure: 318 - name: foo 319 type: DA,21686148-6449-6E6F-744E-656564454649 320 size: 1M 321 role: %[1]s 322 - name: bar 323 type: DA,21686148-6449-6E6F-744E-656564454649 324 size: 1M 325 role: %[1]s 326 `, role) 327 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 328 329 ginfo, err := gadget.ReadInfo(s.dir, nil) 330 c.Assert(err, IsNil) 331 err = gadget.Validate(ginfo, nil, nil) 332 c.Assert(err, ErrorMatches, fmt.Sprintf(`invalid volume "pc": cannot have more than one partition with %s role`, role)) 333 } 334 } 335 336 func (s *validateGadgetTestSuite) TestRuleValidateHybridGadget(c *C) { 337 // this is the kind of volumes setup recommended to be 338 // prepared for a possible UC18 -> UC20 transition 339 hybridyGadgetYaml := []byte(`volumes: 340 hybrid: 341 bootloader: grub 342 structure: 343 - name: mbr 344 type: mbr 345 size: 440 346 content: 347 - image: pc-boot.img 348 - name: BIOS Boot 349 type: DA,21686148-6449-6E6F-744E-656564454649 350 size: 1M 351 offset: 1M 352 offset-write: mbr+92 353 content: 354 - image: pc-core.img 355 - name: EFI System 356 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 357 filesystem: vfat 358 filesystem-label: system-boot 359 size: 1200M 360 content: 361 - source: grubx64.efi 362 target: EFI/boot/grubx64.efi 363 - source: shim.efi.signed 364 target: EFI/boot/bootx64.efi 365 - source: mmx64.efi 366 target: EFI/boot/mmx64.efi 367 - source: grub.cfg 368 target: EFI/ubuntu/grub.cfg 369 - name: Ubuntu Boot 370 type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 371 filesystem: ext4 372 filesystem-label: ubuntu-boot 373 size: 750M 374 `) 375 376 constraints := &modelConstraints{ 377 classic: false, 378 } 379 giMeta, err := gadget.InfoFromGadgetYaml(hybridyGadgetYaml, constraints) 380 c.Assert(err, IsNil) 381 382 err = gadget.Validate(giMeta, constraints, nil) 383 c.Check(err, IsNil) 384 } 385 386 func (s *validateGadgetTestSuite) TestValidateContentMissingRawContent(c *C) { 387 var gadgetYamlContent = ` 388 volumes: 389 pc: 390 bootloader: grub 391 structure: 392 - name: foo 393 type: DA,21686148-6449-6E6F-744E-656564454649 394 size: 1M 395 offset: 1M 396 content: 397 - image: foo.img 398 399 ` 400 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 401 402 ginfo, err := gadget.ReadInfo(s.dir, nil) 403 c.Assert(err, IsNil) 404 err = gadget.ValidateContent(ginfo, s.dir) 405 c.Assert(err, ErrorMatches, `invalid layout of volume "pc": cannot lay out structure #0 \("foo"\): content "foo.img": stat .*/foo.img: no such file or directory`) 406 } 407 408 func (s *validateGadgetTestSuite) TestValidateContentMultiVolumeContent(c *C) { 409 var gadgetYamlContent = ` 410 volumes: 411 first: 412 bootloader: grub 413 structure: 414 - name: first-foo 415 type: DA,21686148-6449-6E6F-744E-656564454649 416 size: 1M 417 content: 418 - image: first.img 419 second: 420 structure: 421 - name: second-foo 422 type: DA,21686148-6449-6E6F-744E-656564454649 423 size: 1M 424 content: 425 - image: second.img 426 427 ` 428 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 429 // only content for the first volume 430 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 431 432 ginfo, err := gadget.ReadInfo(s.dir, nil) 433 c.Assert(err, IsNil) 434 err = gadget.ValidateContent(ginfo, s.dir) 435 c.Assert(err, ErrorMatches, `invalid layout of volume "second": cannot lay out structure #0 \("second-foo"\): content "second.img": stat .*/second.img: no such file or directory`) 436 } 437 438 func (s *validateGadgetTestSuite) TestValidateContentFilesystemContent(c *C) { 439 var gadgetYamlContent = ` 440 volumes: 441 bad: 442 bootloader: grub 443 structure: 444 - name: bad-struct 445 type: DA,21686148-6449-6E6F-744E-656564454649 446 size: 1M 447 filesystem: ext4 448 content: 449 - source: foo/ 450 target: / 451 452 ` 453 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 454 455 ginfo, err := gadget.ReadInfo(s.dir, nil) 456 c.Assert(err, IsNil) 457 err = gadget.ValidateContent(ginfo, s.dir) 458 c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: source path does not exist`) 459 460 // make it a file, which conflicts with foo/ as 'source' 461 fooPath := filepath.Join(s.dir, "foo") 462 makeSizedFile(c, fooPath, 1, nil) 463 err = gadget.ValidateContent(ginfo, s.dir) 464 c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: cannot specify trailing / for a source which is not a directory`) 465 466 // make it a directory 467 err = os.Remove(fooPath) 468 c.Assert(err, IsNil) 469 err = os.Mkdir(fooPath, 0755) 470 c.Assert(err, IsNil) 471 // validate should no longer complain 472 err = gadget.ValidateContent(ginfo, s.dir) 473 c.Assert(err, IsNil) 474 } 475 476 var gadgetYamlContentNoSave = ` 477 volumes: 478 vol1: 479 bootloader: grub 480 structure: 481 - name: ubuntu-seed 482 role: system-seed 483 type: DA,21686148-6449-6E6F-744E-656564454649 484 size: 1M 485 filesystem: ext4 486 - name: ubuntu-boot 487 type: DA,21686148-6449-6E6F-744E-656564454649 488 size: 1M 489 filesystem: ext4 490 - name: ubuntu-data 491 role: system-data 492 type: DA,21686148-6449-6E6F-744E-656564454649 493 size: 1M 494 filesystem: ext4 495 ` 496 497 var gadgetYamlContentWithSave = gadgetYamlContentNoSave + ` 498 - name: ubuntu-save 499 role: system-save 500 type: DA,21686148-6449-6E6F-744E-656564454649 501 size: 1M 502 filesystem: ext4 503 ` 504 505 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportErr(c *C) { 506 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentNoSave)) 507 508 mod := &modelConstraints{systemSeed: true} 509 ginfo, err := gadget.ReadInfo(s.dir, mod) 510 c.Assert(err, IsNil) 511 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 512 EncryptedData: true, 513 }) 514 c.Assert(err, ErrorMatches, `gadget does not support encrypted data: volume "vol1" has no structure with system-save role`) 515 } 516 517 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportHappy(c *C) { 518 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentWithSave)) 519 mod := &modelConstraints{systemSeed: true} 520 ginfo, err := gadget.ReadInfo(s.dir, mod) 521 c.Assert(err, IsNil) 522 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 523 EncryptedData: true, 524 }) 525 c.Assert(err, IsNil) 526 }