github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/boot/modeenv_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 boot_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "strings" 30 31 "github.com/mvo5/goconfigparser" 32 . "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/testutil" 38 ) 39 40 // baseBootSuite is used to setup the common test environment 41 type modeenvSuite struct { 42 testutil.BaseTest 43 44 tmpdir string 45 mockModeenvPath string 46 } 47 48 var _ = Suite(&modeenvSuite{}) 49 50 func (s *modeenvSuite) SetUpTest(c *C) { 51 s.tmpdir = c.MkDir() 52 s.mockModeenvPath = filepath.Join(s.tmpdir, dirs.SnapModeenvFile) 53 } 54 55 func (s *modeenvSuite) TestKnownKnown(c *C) { 56 // double check keys as found with reflect 57 c.Check(boot.ModeenvKnownKeys, DeepEquals, map[string]bool{ 58 "mode": true, 59 "recovery_system": true, 60 "current_recovery_systems": true, 61 "good_recovery_systems": true, 62 "boot_flags": true, 63 // keep this comment to make old go fmt happy 64 "base": true, 65 "try_base": true, 66 "base_status": true, 67 "current_kernels": true, 68 "model": true, 69 "grade": true, 70 "model_sign_key_id": true, 71 "try_model": true, 72 "try_grade": true, 73 "try_model_sign_key_id": true, 74 // keep this comment to make old go fmt happy 75 "current_kernel_command_lines": true, 76 "current_trusted_boot_assets": true, 77 "current_trusted_recovery_boot_assets": true, 78 }) 79 } 80 81 func (s *modeenvSuite) TestReadEmptyErrors(c *C) { 82 modeenv, err := boot.ReadModeenv("/no/such/file") 83 c.Assert(os.IsNotExist(err), Equals, true) 84 c.Assert(modeenv, IsNil) 85 } 86 87 func (s *modeenvSuite) makeMockModeenvFile(c *C, content string) { 88 err := os.MkdirAll(filepath.Dir(s.mockModeenvPath), 0755) 89 c.Assert(err, IsNil) 90 err = ioutil.WriteFile(s.mockModeenvPath, []byte(content), 0644) 91 c.Assert(err, IsNil) 92 } 93 94 func (s *modeenvSuite) TestWasReadSanity(c *C) { 95 modeenv := &boot.Modeenv{} 96 c.Check(modeenv.WasRead(), Equals, false) 97 } 98 99 func (s *modeenvSuite) TestReadEmpty(c *C) { 100 s.makeMockModeenvFile(c, "") 101 102 modeenv, err := boot.ReadModeenv(s.tmpdir) 103 c.Assert(err, ErrorMatches, "internal error: mode is unset") 104 c.Assert(modeenv, IsNil) 105 } 106 107 func (s *modeenvSuite) TestReadMode(c *C) { 108 s.makeMockModeenvFile(c, "mode=run") 109 110 modeenv, err := boot.ReadModeenv(s.tmpdir) 111 c.Assert(err, IsNil) 112 c.Check(modeenv.Mode, Equals, "run") 113 c.Check(modeenv.RecoverySystem, Equals, "") 114 c.Check(modeenv.Base, Equals, "") 115 } 116 117 func (s *modeenvSuite) TestDeepEqualDiskVsMemoryInvariant(c *C) { 118 s.makeMockModeenvFile(c, `mode=recovery 119 recovery_system=20191126 120 base=core20_123.snap 121 try_base=core20_124.snap 122 base_status=try 123 `) 124 125 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 126 c.Assert(err, IsNil) 127 inMemoryModeenv := &boot.Modeenv{ 128 Mode: "recovery", 129 RecoverySystem: "20191126", 130 Base: "core20_123.snap", 131 TryBase: "core20_124.snap", 132 BaseStatus: "try", 133 } 134 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true) 135 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true) 136 } 137 138 func (s *modeenvSuite) TestCopyDeepEquals(c *C) { 139 s.makeMockModeenvFile(c, `mode=recovery 140 recovery_system=20191126 141 base=core20_123.snap 142 try_base=core20_124.snap 143 base_status=try 144 current_trusted_boot_assets={"thing1":["hash1","hash2"],"thing2":["hash3"]} 145 current_kernel_command_lines=["foo", "bar"] 146 `) 147 148 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 149 c.Assert(err, IsNil) 150 inMemoryModeenv := &boot.Modeenv{ 151 Mode: "recovery", 152 RecoverySystem: "20191126", 153 Base: "core20_123.snap", 154 TryBase: "core20_124.snap", 155 BaseStatus: "try", 156 CurrentTrustedBootAssets: boot.BootAssetsMap{ 157 "thing1": {"hash1", "hash2"}, 158 "thing2": {"hash3"}, 159 }, 160 CurrentKernelCommandLines: boot.BootCommandLines{ 161 "foo", "bar", 162 }, 163 } 164 165 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true) 166 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true) 167 168 diskModeenv2, err := diskModeenv.Copy() 169 c.Assert(err, IsNil) 170 c.Assert(diskModeenv.DeepEqual(diskModeenv2), Equals, true) 171 c.Assert(diskModeenv2.DeepEqual(diskModeenv), Equals, true) 172 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv2), Equals, true) 173 c.Assert(diskModeenv2.DeepEqual(inMemoryModeenv), Equals, true) 174 175 inMemoryModeenv2, err := inMemoryModeenv.Copy() 176 c.Assert(err, IsNil) 177 c.Assert(inMemoryModeenv.DeepEqual(inMemoryModeenv2), Equals, true) 178 c.Assert(inMemoryModeenv2.DeepEqual(inMemoryModeenv), Equals, true) 179 c.Assert(inMemoryModeenv2.DeepEqual(diskModeenv), Equals, true) 180 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv2), Equals, true) 181 } 182 183 func (s *modeenvSuite) TestCopyDiskWriteWorks(c *C) { 184 s.makeMockModeenvFile(c, `mode=recovery 185 recovery_system=20191126 186 base=core20_123.snap 187 try_base=core20_124.snap 188 base_status=try 189 `) 190 191 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 192 c.Assert(err, IsNil) 193 dupDiskModeenv, err := diskModeenv.Copy() 194 c.Assert(err, IsNil) 195 196 // move the original file out of the way 197 err = os.Rename(dirs.SnapModeenvFileUnder(s.tmpdir), dirs.SnapModeenvFileUnder(s.tmpdir)+".orig") 198 c.Assert(err, IsNil) 199 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileAbsent) 200 201 // write the duplicate, it should write to the same original location and it 202 // should be the same content 203 err = dupDiskModeenv.Write() 204 c.Assert(err, IsNil) 205 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FilePresent) 206 origBytes, err := ioutil.ReadFile(dirs.SnapModeenvFileUnder(s.tmpdir) + ".orig") 207 c.Assert(err, IsNil) 208 // the files should be the same 209 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileEquals, string(origBytes)) 210 } 211 212 func (s *modeenvSuite) TestCopyMemoryWriteFails(c *C) { 213 inMemoryModeenv := &boot.Modeenv{ 214 Mode: "recovery", 215 RecoverySystem: "20191126", 216 Base: "core20_123.snap", 217 TryBase: "core20_124.snap", 218 BaseStatus: "try", 219 } 220 dupInMemoryModeenv, err := inMemoryModeenv.Copy() 221 c.Assert(err, IsNil) 222 223 // write the duplicate, it should fail 224 err = dupInMemoryModeenv.Write() 225 c.Assert(err, ErrorMatches, "internal error: must use WriteTo with modeenv not read from disk") 226 } 227 228 func (s *modeenvSuite) TestDeepEquals(c *C) { 229 // start with two identical modeenvs 230 modeenv1 := &boot.Modeenv{ 231 Mode: "recovery", 232 RecoverySystem: "20191126", 233 CurrentRecoverySystems: []string{"1", "2"}, 234 GoodRecoverySystems: []string{"3"}, 235 236 Base: "core20_123.snap", 237 TryBase: "core20_124.snap", 238 BaseStatus: "try", 239 CurrentKernels: []string{"k1", "k2"}, 240 241 Model: "model", 242 BrandID: "brand", 243 Grade: "secured", 244 ModelSignKeyID: "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn", 245 246 BootFlags: []string{"foo", "factory"}, 247 248 CurrentTrustedBootAssets: boot.BootAssetsMap{ 249 "thing1": {"hash1", "hash2"}, 250 "thing2": {"hash3"}, 251 }, 252 253 CurrentKernelCommandLines: boot.BootCommandLines{ 254 "foo", 255 "foo bar", 256 }, 257 } 258 259 modeenv2 := &boot.Modeenv{ 260 Mode: "recovery", 261 RecoverySystem: "20191126", 262 CurrentRecoverySystems: []string{"1", "2"}, 263 GoodRecoverySystems: []string{"3"}, 264 265 Base: "core20_123.snap", 266 TryBase: "core20_124.snap", 267 BaseStatus: "try", 268 CurrentKernels: []string{"k1", "k2"}, 269 270 Model: "model", 271 BrandID: "brand", 272 Grade: "secured", 273 ModelSignKeyID: "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn", 274 275 BootFlags: []string{"foo", "factory"}, 276 277 CurrentTrustedBootAssets: boot.BootAssetsMap{ 278 "thing1": {"hash1", "hash2"}, 279 "thing2": {"hash3"}, 280 }, 281 282 CurrentKernelCommandLines: boot.BootCommandLines{ 283 "foo", 284 "foo bar", 285 }, 286 } 287 288 // same object should be the same 289 c.Assert(modeenv1.DeepEqual(modeenv1), Equals, true) 290 291 // no difference should be the same at the start 292 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 293 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, true) 294 295 // invert CurrentKernels 296 modeenv2.CurrentKernels = []string{"k2", "k1"} 297 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 298 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 299 300 // make CurrentKernels capitalized 301 modeenv2.CurrentKernels = []string{"K1", "k2"} 302 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 303 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 304 305 // make CurrentKernels disappear 306 modeenv2.CurrentKernels = nil 307 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 308 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 309 310 // make it identical again 311 modeenv2.CurrentKernels = []string{"k1", "k2"} 312 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 313 // change kernel command lines 314 modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{ 315 // reversed order 316 "foo bar", 317 "foo", 318 } 319 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 320 // clear kernel command lines list 321 modeenv2.CurrentKernelCommandLines = nil 322 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 323 324 // make it identical again 325 modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{ 326 "foo", 327 "foo bar", 328 } 329 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 330 331 // change the list of current recovery systems 332 modeenv2.CurrentRecoverySystems = append(modeenv2.CurrentRecoverySystems, "1234") 333 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 334 // make it identical again 335 modeenv2.CurrentRecoverySystems = []string{"1", "2"} 336 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 337 338 // change the list of good recovery systems 339 modeenv2.GoodRecoverySystems = append(modeenv2.GoodRecoverySystems, "999") 340 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 341 // restore it 342 modeenv2.GoodRecoverySystems = modeenv2.GoodRecoverySystems[:len(modeenv2.GoodRecoverySystems)-1] 343 344 // change the sign key ID 345 modeenv2.ModelSignKeyID = "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu" 346 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 347 } 348 349 func (s *modeenvSuite) TestReadModeWithRecoverySystem(c *C) { 350 s.makeMockModeenvFile(c, `mode=recovery 351 recovery_system=20191126 352 `) 353 354 modeenv, err := boot.ReadModeenv(s.tmpdir) 355 c.Assert(err, IsNil) 356 c.Check(modeenv.Mode, Equals, "recovery") 357 c.Check(modeenv.RecoverySystem, Equals, "20191126") 358 } 359 360 func (s *modeenvSuite) TestReadModeenvWithUnknownKeysKeepsWrites(c *C) { 361 s.makeMockModeenvFile(c, `first_unknown=thing 362 mode=recovery 363 recovery_system=20191126 364 unknown_key=some unknown value 365 a_key=other 366 `) 367 368 modeenv, err := boot.ReadModeenv(s.tmpdir) 369 c.Assert(err, IsNil) 370 c.Check(modeenv.Mode, Equals, "recovery") 371 c.Check(modeenv.RecoverySystem, Equals, "20191126") 372 373 c.Assert(modeenv.Write(), IsNil) 374 375 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=recovery 376 recovery_system=20191126 377 a_key=other 378 first_unknown=thing 379 unknown_key=some unknown value 380 `) 381 } 382 383 func (s *modeenvSuite) TestReadModeenvWithUnknownKeysDeepEqualsSameWithoutUnknownKeys(c *C) { 384 s.makeMockModeenvFile(c, `first_unknown=thing 385 mode=recovery 386 recovery_system=20191126 387 try_base=core20_124.snap 388 base_status=try 389 unknown_key=some unknown value 390 current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]} 391 current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]} 392 a_key=other 393 `) 394 395 modeenvWithExtraKeys, err := boot.ReadModeenv(s.tmpdir) 396 c.Assert(err, IsNil) 397 c.Check(modeenvWithExtraKeys.Mode, Equals, "recovery") 398 c.Check(modeenvWithExtraKeys.RecoverySystem, Equals, "20191126") 399 400 // should be the same as one that with just those keys in memory 401 c.Assert(modeenvWithExtraKeys.DeepEqual(&boot.Modeenv{ 402 Mode: "recovery", 403 RecoverySystem: "20191126", 404 TryBase: "core20_124.snap", 405 BaseStatus: boot.TryStatus, 406 CurrentTrustedBootAssets: boot.BootAssetsMap{ 407 "grubx64.efi": []string{"hash1", "hash2"}, 408 }, 409 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 410 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 411 "grubx64.efi": []string{"recovery-hash1"}, 412 }, 413 }), Equals, true) 414 } 415 416 func (s *modeenvSuite) TestReadModeWithBase(c *C) { 417 s.makeMockModeenvFile(c, `mode=recovery 418 recovery_system=20191126 419 base=core20_123.snap 420 try_base=core20_124.snap 421 base_status=try 422 `) 423 424 modeenv, err := boot.ReadModeenv(s.tmpdir) 425 c.Assert(err, IsNil) 426 c.Check(modeenv.Mode, Equals, "recovery") 427 c.Check(modeenv.RecoverySystem, Equals, "20191126") 428 c.Check(modeenv.Base, Equals, "core20_123.snap") 429 c.Check(modeenv.TryBase, Equals, "core20_124.snap") 430 c.Check(modeenv.BaseStatus, Equals, boot.TryStatus) 431 } 432 433 func (s *modeenvSuite) TestReadModeWithGrade(c *C) { 434 s.makeMockModeenvFile(c, `mode=run 435 grade=dangerous 436 `) 437 modeenv, err := boot.ReadModeenv(s.tmpdir) 438 c.Assert(err, IsNil) 439 c.Check(modeenv.Mode, Equals, "run") 440 c.Check(modeenv.Grade, Equals, "dangerous") 441 442 s.makeMockModeenvFile(c, `mode=run 443 grade=some-random-grade-string 444 `) 445 modeenv, err = boot.ReadModeenv(s.tmpdir) 446 c.Assert(err, IsNil) 447 c.Check(modeenv.Mode, Equals, "run") 448 c.Check(modeenv.Grade, Equals, "some-random-grade-string") 449 } 450 451 func (s *modeenvSuite) TestReadModeWithModel(c *C) { 452 tt := []struct { 453 entry string 454 model, brand string 455 }{ 456 { 457 entry: "my-brand/my-model", 458 brand: "my-brand", 459 model: "my-model", 460 }, { 461 entry: "my-brand/", 462 }, { 463 entry: "my-model/", 464 }, { 465 entry: "foobar", 466 }, { 467 entry: "/", 468 }, { 469 entry: ",", 470 }, { 471 entry: "", 472 }, 473 } 474 475 for _, t := range tt { 476 s.makeMockModeenvFile(c, `mode=run 477 model=`+t.entry+"\n") 478 modeenv, err := boot.ReadModeenv(s.tmpdir) 479 c.Assert(err, IsNil) 480 c.Check(modeenv.Mode, Equals, "run") 481 c.Check(modeenv.Model, Equals, t.model) 482 c.Check(modeenv.BrandID, Equals, t.brand) 483 } 484 } 485 486 func (s *modeenvSuite) TestReadModeWithCurrentKernels(c *C) { 487 488 tt := []struct { 489 kernelString string 490 expectedKernels []string 491 }{ 492 { 493 "pc-kernel_1.snap", 494 []string{"pc-kernel_1.snap"}, 495 }, 496 { 497 "pc-kernel_1.snap,pc-kernel_2.snap", 498 []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 499 }, 500 { 501 "pc-kernel_1.snap,,,,,pc-kernel_2.snap", 502 []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 503 }, 504 // we should be robust in parsing the modeenv against garbage 505 { 506 `pc-kernel_1.snap,this-is-not-a-real-snap$%^&^%$#@#$%^%"$,pc-kernel_2.snap`, 507 []string{"pc-kernel_1.snap", `this-is-not-a-real-snap$%^&^%$#@#$%^%"$`, "pc-kernel_2.snap"}, 508 }, 509 {",,,", nil}, 510 {"", nil}, 511 } 512 513 for _, t := range tt { 514 s.makeMockModeenvFile(c, `mode=recovery 515 recovery_system=20191126 516 current_kernels=`+t.kernelString+"\n") 517 518 modeenv, err := boot.ReadModeenv(s.tmpdir) 519 c.Assert(err, IsNil) 520 c.Check(modeenv.Mode, Equals, "recovery") 521 c.Check(modeenv.RecoverySystem, Equals, "20191126") 522 c.Check(len(modeenv.CurrentKernels), Equals, len(t.expectedKernels)) 523 if len(t.expectedKernels) != 0 { 524 c.Check(modeenv.CurrentKernels, DeepEquals, t.expectedKernels) 525 } 526 } 527 } 528 529 func (s *modeenvSuite) TestWriteToNonExisting(c *C) { 530 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 531 532 modeenv := &boot.Modeenv{Mode: "run"} 533 err := modeenv.WriteTo(s.tmpdir) 534 c.Assert(err, IsNil) 535 536 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=run\n") 537 } 538 539 func (s *modeenvSuite) TestWriteToExisting(c *C) { 540 s.makeMockModeenvFile(c, "mode=run") 541 542 modeenv, err := boot.ReadModeenv(s.tmpdir) 543 c.Assert(err, IsNil) 544 modeenv.Mode = "recovery" 545 err = modeenv.WriteTo(s.tmpdir) 546 c.Assert(err, IsNil) 547 548 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n") 549 } 550 551 func (s *modeenvSuite) TestWriteExisting(c *C) { 552 s.makeMockModeenvFile(c, "mode=run") 553 554 modeenv, err := boot.ReadModeenv(s.tmpdir) 555 c.Assert(err, IsNil) 556 modeenv.Mode = "recovery" 557 err = modeenv.Write() 558 c.Assert(err, IsNil) 559 560 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n") 561 } 562 563 func (s *modeenvSuite) TestWriteFreshError(c *C) { 564 modeenv := &boot.Modeenv{Mode: "recovery"} 565 566 err := modeenv.Write() 567 c.Assert(err, ErrorMatches, `internal error: must use WriteTo with modeenv not read from disk`) 568 } 569 570 func (s *modeenvSuite) TestWriteIncompleteModelBrand(c *C) { 571 modeenv := &boot.Modeenv{ 572 Mode: "run", 573 Grade: "dangerous", 574 } 575 576 err := modeenv.WriteTo(s.tmpdir) 577 c.Assert(err, ErrorMatches, `internal error: model is unset`) 578 579 modeenv.Model = "bar" 580 err = modeenv.WriteTo(s.tmpdir) 581 c.Assert(err, ErrorMatches, `internal error: brand is unset`) 582 583 modeenv.BrandID = "foo" 584 modeenv.TryGrade = "dangerous" 585 err = modeenv.WriteTo(s.tmpdir) 586 c.Assert(err, ErrorMatches, `internal error: try model is unset`) 587 588 modeenv.TryModel = "bar" 589 err = modeenv.WriteTo(s.tmpdir) 590 c.Assert(err, ErrorMatches, `internal error: try brand is unset`) 591 592 modeenv.TryBrandID = "foo" 593 err = modeenv.WriteTo(s.tmpdir) 594 c.Assert(err, IsNil) 595 } 596 597 func (s *modeenvSuite) TestWriteToNonExistingFull(c *C) { 598 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 599 600 modeenv := &boot.Modeenv{ 601 Mode: "run", 602 RecoverySystem: "20191128", 603 CurrentRecoverySystems: []string{"20191128", "2020-02-03", "20240101-FOO"}, 604 // keep this comment to make gofmt 1.9 happy 605 Base: "core20_321.snap", 606 TryBase: "core20_322.snap", 607 BaseStatus: boot.TryStatus, 608 CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 609 } 610 err := modeenv.WriteTo(s.tmpdir) 611 c.Assert(err, IsNil) 612 613 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 614 recovery_system=20191128 615 current_recovery_systems=20191128,2020-02-03,20240101-FOO 616 base=core20_321.snap 617 try_base=core20_322.snap 618 base_status=try 619 current_kernels=pc-kernel_1.snap,pc-kernel_2.snap 620 `) 621 } 622 623 func (s *modeenvSuite) TestReadRecoverySystems(c *C) { 624 tt := []struct { 625 systemsString string 626 expectedSystems []string 627 }{ 628 { 629 "20191126", 630 []string{"20191126"}, 631 }, { 632 "20191128,2020-02-03,20240101-FOO", 633 []string{"20191128", "2020-02-03", "20240101-FOO"}, 634 }, 635 {",,,", nil}, 636 {"", nil}, 637 } 638 639 for _, t := range tt { 640 c.Logf("tc: %q", t.systemsString) 641 s.makeMockModeenvFile(c, fmt.Sprintf(`mode=recovery 642 recovery_system=20191126 643 current_recovery_systems=%[1]s 644 good_recovery_systems=%[1]s 645 `, t.systemsString)) 646 647 modeenv, err := boot.ReadModeenv(s.tmpdir) 648 c.Assert(err, IsNil) 649 c.Check(modeenv.Mode, Equals, "recovery") 650 c.Check(modeenv.RecoverySystem, Equals, "20191126") 651 c.Check(modeenv.CurrentRecoverySystems, DeepEquals, t.expectedSystems) 652 c.Check(modeenv.GoodRecoverySystems, DeepEquals, t.expectedSystems) 653 } 654 } 655 656 type fancyDataBothMarshallers struct { 657 Foo []string 658 } 659 660 func (f *fancyDataBothMarshallers) MarshalModeenvValue() (string, error) { 661 return strings.Join(f.Foo, "#"), nil 662 } 663 664 func (f *fancyDataBothMarshallers) UnmarshalModeenvValue(v string) error { 665 f.Foo = strings.Split(v, "#") 666 return nil 667 } 668 669 func (f *fancyDataBothMarshallers) MarshalJSON() ([]byte, error) { 670 return nil, fmt.Errorf("unexpected call to JSON marshaller") 671 } 672 673 func (f *fancyDataBothMarshallers) UnmarshalJSON(data []byte) error { 674 return fmt.Errorf("unexpected call to JSON unmarshaller") 675 } 676 677 type fancyDataJSONOnly struct { 678 Foo []string 679 } 680 681 func (f *fancyDataJSONOnly) MarshalJSON() ([]byte, error) { 682 return json.Marshal(f.Foo) 683 } 684 685 func (f *fancyDataJSONOnly) UnmarshalJSON(data []byte) error { 686 return json.Unmarshal(data, &f.Foo) 687 } 688 689 func (s *modeenvSuite) TestFancyMarshalUnmarshal(c *C) { 690 var buf bytes.Buffer 691 692 dboth := fancyDataBothMarshallers{Foo: []string{"1", "two"}} 693 err := boot.MarshalModeenvEntryTo(&buf, "fancy", &dboth) 694 c.Assert(err, IsNil) 695 c.Check(buf.String(), Equals, `fancy=1#two 696 `) 697 698 djson := fancyDataJSONOnly{Foo: []string{"1", "two", "with\nnewline"}} 699 err = boot.MarshalModeenvEntryTo(&buf, "fancy_json", &djson) 700 c.Assert(err, IsNil) 701 c.Check(buf.String(), Equals, `fancy=1#two 702 fancy_json=["1","two","with\nnewline"] 703 `) 704 705 cfg := goconfigparser.New() 706 cfg.AllowNoSectionHeader = true 707 err = cfg.Read(&buf) 708 c.Assert(err, IsNil) 709 710 var dbothRev fancyDataBothMarshallers 711 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy", &dbothRev) 712 c.Assert(err, IsNil) 713 c.Check(dbothRev, DeepEquals, dboth) 714 715 var djsonRev fancyDataJSONOnly 716 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev) 717 c.Assert(err, IsNil) 718 c.Check(djsonRev, DeepEquals, djson) 719 } 720 721 func (s *modeenvSuite) TestFancyUnmarshalJSONEmpty(c *C) { 722 var buf bytes.Buffer 723 724 cfg := goconfigparser.New() 725 cfg.AllowNoSectionHeader = true 726 err := cfg.Read(&buf) 727 c.Assert(err, IsNil) 728 729 var djsonRev fancyDataJSONOnly 730 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev) 731 c.Assert(err, IsNil) 732 c.Check(djsonRev.Foo, IsNil) 733 } 734 735 func (s *modeenvSuite) TestMarshalCurrentTrustedBootAssets(c *C) { 736 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 737 738 modeenv := &boot.Modeenv{ 739 Mode: "run", 740 RecoverySystem: "20191128", 741 CurrentTrustedBootAssets: boot.BootAssetsMap{ 742 "grubx64.efi": []string{"hash1", "hash2"}, 743 }, 744 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 745 "grubx64.efi": []string{"recovery-hash1"}, 746 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 747 }, 748 } 749 err := modeenv.WriteTo(s.tmpdir) 750 c.Assert(err, IsNil) 751 752 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 753 recovery_system=20191128 754 current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]} 755 current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]} 756 `) 757 758 modeenvRead, err := boot.ReadModeenv(s.tmpdir) 759 c.Assert(err, IsNil) 760 c.Assert(modeenvRead.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{ 761 "grubx64.efi": []string{"hash1", "hash2"}, 762 }) 763 c.Assert(modeenvRead.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{ 764 "grubx64.efi": []string{"recovery-hash1"}, 765 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 766 }) 767 } 768 769 func (s *modeenvSuite) TestMarshalKernelCommandLines(c *C) { 770 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 771 772 modeenv := &boot.Modeenv{ 773 Mode: "run", 774 RecoverySystem: "20191128", 775 CurrentKernelCommandLines: boot.BootCommandLines{ 776 `snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`, 777 `snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`, 778 }, 779 } 780 err := modeenv.WriteTo(s.tmpdir) 781 c.Assert(err, IsNil) 782 783 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 784 recovery_system=20191128 785 current_kernel_command_lines=["snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8","snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8"] 786 `) 787 788 modeenvRead, err := boot.ReadModeenv(s.tmpdir) 789 c.Assert(err, IsNil) 790 c.Assert(modeenvRead.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 791 `snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`, 792 `snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`, 793 }) 794 } 795 796 func (s *modeenvSuite) TestModeenvWithModelGradeSignKeyID(c *C) { 797 s.makeMockModeenvFile(c, `mode=run 798 model=canonical/ubuntu-core-20-amd64 799 grade=dangerous 800 model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn 801 try_model=developer1/testkeys-snapd-secured-core-20-amd64 802 try_grade=secured 803 try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu 804 `) 805 806 modeenv, err := boot.ReadModeenv(s.tmpdir) 807 c.Assert(err, IsNil) 808 c.Check(modeenv.Model, Equals, "ubuntu-core-20-amd64") 809 c.Check(modeenv.BrandID, Equals, "canonical") 810 c.Check(modeenv.Grade, Equals, "dangerous") 811 c.Check(modeenv.ModelSignKeyID, Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn") 812 // candidate model 813 c.Check(modeenv.TryModel, Equals, "testkeys-snapd-secured-core-20-amd64") 814 c.Check(modeenv.TryBrandID, Equals, "developer1") 815 c.Check(modeenv.TryGrade, Equals, "secured") 816 c.Check(modeenv.TryModelSignKeyID, Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu") 817 818 // change some model data now 819 modeenv.Model = "testkeys-snapd-signed-core-20-amd64" 820 modeenv.BrandID = "developer1" 821 modeenv.Grade = "signed" 822 modeenv.ModelSignKeyID = "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu" 823 824 modeenv.TryModel = "bar" 825 modeenv.TryBrandID = "foo" 826 modeenv.TryGrade = "dangerous" 827 modeenv.TryModelSignKeyID = "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn" 828 829 // and write it 830 c.Assert(modeenv.Write(), IsNil) 831 832 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 833 model=developer1/testkeys-snapd-signed-core-20-amd64 834 grade=signed 835 model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu 836 try_model=foo/bar 837 try_grade=dangerous 838 try_model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn 839 `) 840 } 841 842 func (s *modeenvSuite) TestModelForSealing(c *C) { 843 s.makeMockModeenvFile(c, `mode=run 844 model=canonical/ubuntu-core-20-amd64 845 grade=dangerous 846 model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn 847 try_model=developer1/testkeys-snapd-secured-core-20-amd64 848 try_grade=secured 849 try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu 850 `) 851 852 modeenv, err := boot.ReadModeenv(s.tmpdir) 853 c.Assert(err, IsNil) 854 855 modelForSealing := modeenv.ModelForSealing() 856 c.Check(modelForSealing.Model(), Equals, "ubuntu-core-20-amd64") 857 c.Check(modelForSealing.BrandID(), Equals, "canonical") 858 c.Check(modelForSealing.Grade(), Equals, asserts.ModelGrade("dangerous")) 859 c.Check(modelForSealing.SignKeyID(), Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn") 860 c.Check(modelForSealing.Series(), Equals, "16") 861 c.Check(boot.ModelUniqueID(modelForSealing), Equals, 862 "canonical/ubuntu-core-20-amd64,dangerous,9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn") 863 864 tryModelForSealing := modeenv.TryModelForSealing() 865 c.Check(tryModelForSealing.Model(), Equals, "testkeys-snapd-secured-core-20-amd64") 866 c.Check(tryModelForSealing.BrandID(), Equals, "developer1") 867 c.Check(tryModelForSealing.Grade(), Equals, asserts.ModelGrade("secured")) 868 c.Check(tryModelForSealing.SignKeyID(), Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu") 869 c.Check(tryModelForSealing.Series(), Equals, "16") 870 c.Check(boot.ModelUniqueID(tryModelForSealing), Equals, 871 "developer1/testkeys-snapd-secured-core-20-amd64,secured,EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu") 872 }