Files
snapd/boot/modeenv_test.go
Miguel Pires 79c5ac14b2 many: remove usages of deprecated io/ioutil package (#13768)
* many: remove usages of deprecated io/ioutil package

Signed-off-by: Miguel Pires <miguel.pires@canonical.com>

* .golangci.yml: remove errcheck ignore rule for io/ioutil

Signed-off-by: Miguel Pires <miguel.pires@canonical.com>

* run-checks: prevent new usages of io/ioutil

Signed-off-by: Miguel Pires <miguel.pires@canonical.com>

---------

Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
2024-04-03 23:23:24 +02:00

977 lines
31 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package boot_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/mvo5/goconfigparser"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/testutil"
)
// baseBootSuite is used to setup the common test environment
type modeenvSuite struct {
testutil.BaseTest
tmpdir string
mockModeenvPath string
}
var _ = Suite(&modeenvSuite{})
func (s *modeenvSuite) SetUpTest(c *C) {
s.tmpdir = c.MkDir()
s.mockModeenvPath = filepath.Join(s.tmpdir, dirs.SnapModeenvFile)
}
func (s *modeenvSuite) TestKnownKnown(c *C) {
// double check keys as found with reflect
c.Check(boot.ModeenvKnownKeys, DeepEquals, map[string]bool{
"mode": true,
"recovery_system": true,
"current_recovery_systems": true,
"good_recovery_systems": true,
"boot_flags": true,
// keep this comment to make old go fmt happy
"base": true,
"gadget": true,
"try_base": true,
"base_status": true,
"current_kernels": true,
"model": true,
"classic": true,
"grade": true,
"model_sign_key_id": true,
"try_model": true,
"try_grade": true,
"try_model_sign_key_id": true,
// keep this comment to make old go fmt happy
"current_kernel_command_lines": true,
"current_trusted_boot_assets": true,
"current_trusted_recovery_boot_assets": true,
})
}
func (s *modeenvSuite) TestReadEmptyErrors(c *C) {
modeenv, err := boot.ReadModeenv("/no/such/file")
c.Assert(os.IsNotExist(err), Equals, true)
c.Assert(modeenv, IsNil)
}
func (s *modeenvSuite) makeMockModeenvFile(c *C, content string) {
err := os.MkdirAll(filepath.Dir(s.mockModeenvPath), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(s.mockModeenvPath, []byte(content), 0644)
c.Assert(err, IsNil)
}
func (s *modeenvSuite) TestWasReadValidity(c *C) {
modeenv := &boot.Modeenv{}
c.Check(modeenv.WasRead(), Equals, false)
}
func (s *modeenvSuite) TestReadEmpty(c *C) {
s.makeMockModeenvFile(c, "")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, ErrorMatches, "internal error: mode is unset")
c.Assert(modeenv, IsNil)
}
func (s *modeenvSuite) TestReadMode(c *C) {
s.makeMockModeenvFile(c, "mode=run")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "run")
c.Check(modeenv.RecoverySystem, Equals, "")
c.Check(modeenv.Base, Equals, "")
c.Check(modeenv.Gadget, Equals, "")
}
func (s *modeenvSuite) TestDeepEqualDiskVsMemoryInvariant(c *C) {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
base=core20_123.snap
gadget=pc_1.snap
try_base=core20_124.snap
base_status=try
`)
diskModeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
inMemoryModeenv := &boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
Base: "core20_123.snap",
Gadget: "pc_1.snap",
TryBase: "core20_124.snap",
BaseStatus: "try",
}
c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true)
c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true)
}
func (s *modeenvSuite) TestCopyDeepEquals(c *C) {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
base=core20_123.snap
try_base=core20_124.snap
base_status=try
current_trusted_boot_assets={"thing1":["hash1","hash2"],"thing2":["hash3"]}
current_kernel_command_lines=["foo", "bar"]
`)
diskModeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
inMemoryModeenv := &boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
Base: "core20_123.snap",
TryBase: "core20_124.snap",
BaseStatus: "try",
CurrentTrustedBootAssets: boot.BootAssetsMap{
"thing1": []string{"hash1", "hash2"},
"thing2": []string{"hash3"},
},
CurrentKernelCommandLines: boot.BootCommandLines{
"foo", "bar",
},
}
c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true)
c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true)
diskModeenv2, err := diskModeenv.Copy()
c.Assert(err, IsNil)
c.Assert(diskModeenv.DeepEqual(diskModeenv2), Equals, true)
c.Assert(diskModeenv2.DeepEqual(diskModeenv), Equals, true)
c.Assert(inMemoryModeenv.DeepEqual(diskModeenv2), Equals, true)
c.Assert(diskModeenv2.DeepEqual(inMemoryModeenv), Equals, true)
inMemoryModeenv2, err := inMemoryModeenv.Copy()
c.Assert(err, IsNil)
c.Assert(inMemoryModeenv.DeepEqual(inMemoryModeenv2), Equals, true)
c.Assert(inMemoryModeenv2.DeepEqual(inMemoryModeenv), Equals, true)
c.Assert(inMemoryModeenv2.DeepEqual(diskModeenv), Equals, true)
c.Assert(diskModeenv.DeepEqual(inMemoryModeenv2), Equals, true)
}
func (s *modeenvSuite) TestCopyDiskWriteWorks(c *C) {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
base=core20_123.snap
try_base=core20_124.snap
base_status=try
`)
diskModeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
dupDiskModeenv, err := diskModeenv.Copy()
c.Assert(err, IsNil)
// move the original file out of the way
err = os.Rename(dirs.SnapModeenvFileUnder(s.tmpdir), dirs.SnapModeenvFileUnder(s.tmpdir)+".orig")
c.Assert(err, IsNil)
c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileAbsent)
// write the duplicate, it should write to the same original location and it
// should be the same content
err = dupDiskModeenv.Write()
c.Assert(err, IsNil)
c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FilePresent)
origBytes, err := os.ReadFile(dirs.SnapModeenvFileUnder(s.tmpdir) + ".orig")
c.Assert(err, IsNil)
// the files should be the same
c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileEquals, string(origBytes))
}
func (s *modeenvSuite) TestCopyMemoryWriteFails(c *C) {
inMemoryModeenv := &boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
Base: "core20_123.snap",
TryBase: "core20_124.snap",
BaseStatus: "try",
}
dupInMemoryModeenv, err := inMemoryModeenv.Copy()
c.Assert(err, IsNil)
// write the duplicate, it should fail
err = dupInMemoryModeenv.Write()
c.Assert(err, ErrorMatches, "internal error: must use WriteTo with modeenv not read from disk")
}
func (s *modeenvSuite) TestDeepEquals(c *C) {
// start with two identical modeenvs
modeenv1 := &boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
CurrentRecoverySystems: []string{"1", "2"},
GoodRecoverySystems: []string{"3"},
Base: "core20_123.snap",
TryBase: "core20_124.snap",
BaseStatus: "try",
CurrentKernels: []string{"k1", "k2"},
Model: "model",
BrandID: "brand",
Grade: "secured",
ModelSignKeyID: "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn",
BootFlags: []string{"foo", "factory"},
CurrentTrustedBootAssets: boot.BootAssetsMap{
"thing1": []string{"hash1", "hash2"},
"thing2": []string{"hash3"},
},
CurrentKernelCommandLines: boot.BootCommandLines{
"foo",
"foo bar",
},
}
modeenv2 := &boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
CurrentRecoverySystems: []string{"1", "2"},
GoodRecoverySystems: []string{"3"},
Base: "core20_123.snap",
TryBase: "core20_124.snap",
BaseStatus: "try",
CurrentKernels: []string{"k1", "k2"},
Model: "model",
BrandID: "brand",
Grade: "secured",
ModelSignKeyID: "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn",
BootFlags: []string{"foo", "factory"},
CurrentTrustedBootAssets: boot.BootAssetsMap{
"thing1": []string{"hash1", "hash2"},
"thing2": []string{"hash3"},
},
CurrentKernelCommandLines: boot.BootCommandLines{
"foo",
"foo bar",
},
}
// same object should be the same
c.Assert(modeenv1.DeepEqual(modeenv1), Equals, true)
// no difference should be the same at the start
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true)
c.Assert(modeenv2.DeepEqual(modeenv1), Equals, true)
// invert CurrentKernels
modeenv2.CurrentKernels = []string{"k2", "k1"}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false)
// make CurrentKernels capitalized
modeenv2.CurrentKernels = []string{"K1", "k2"}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false)
// make CurrentKernels disappear
modeenv2.CurrentKernels = nil
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false)
// make it identical again
modeenv2.CurrentKernels = []string{"k1", "k2"}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true)
// change kernel command lines
modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{
// reversed order
"foo bar",
"foo",
}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
// clear kernel command lines list
modeenv2.CurrentKernelCommandLines = nil
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
// make it identical again
modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{
"foo",
"foo bar",
}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true)
// change the list of current recovery systems
modeenv2.CurrentRecoverySystems = append(modeenv2.CurrentRecoverySystems, "1234")
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
// make it identical again
modeenv2.CurrentRecoverySystems = []string{"1", "2"}
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true)
// change the list of good recovery systems
modeenv2.GoodRecoverySystems = append(modeenv2.GoodRecoverySystems, "999")
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
// restore it
modeenv2.GoodRecoverySystems = modeenv2.GoodRecoverySystems[:len(modeenv2.GoodRecoverySystems)-1]
// change the sign key ID
modeenv2.ModelSignKeyID = "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu"
c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false)
}
func (s *modeenvSuite) TestReadModeWithRecoverySystem(c *C) {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "recovery")
c.Check(modeenv.RecoverySystem, Equals, "20191126")
}
func (s *modeenvSuite) TestReadModeenvWithUnknownKeysKeepsWrites(c *C) {
s.makeMockModeenvFile(c, `first_unknown=thing
mode=recovery
recovery_system=20191126
unknown_key=some unknown value
a_key=other
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "recovery")
c.Check(modeenv.RecoverySystem, Equals, "20191126")
c.Assert(modeenv.Write(), IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=recovery
recovery_system=20191126
a_key=other
first_unknown=thing
unknown_key=some unknown value
`)
}
func (s *modeenvSuite) TestReadModeenvWithUnknownKeysDeepEqualsSameWithoutUnknownKeys(c *C) {
s.makeMockModeenvFile(c, `first_unknown=thing
mode=recovery
recovery_system=20191126
try_base=core20_124.snap
base_status=try
unknown_key=some unknown value
current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]}
current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]}
a_key=other
`)
modeenvWithExtraKeys, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenvWithExtraKeys.Mode, Equals, "recovery")
c.Check(modeenvWithExtraKeys.RecoverySystem, Equals, "20191126")
// should be the same as one that with just those keys in memory
c.Assert(modeenvWithExtraKeys.DeepEqual(&boot.Modeenv{
Mode: "recovery",
RecoverySystem: "20191126",
TryBase: "core20_124.snap",
BaseStatus: boot.TryStatus,
CurrentTrustedBootAssets: boot.BootAssetsMap{
"grubx64.efi": []string{"hash1", "hash2"},
},
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
"bootx64.efi": []string{"shimhash1", "shimhash2"},
"grubx64.efi": []string{"recovery-hash1"},
},
}), Equals, true)
}
func (s *modeenvSuite) TestReadModeWithBase(c *C) {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
base=core20_123.snap
try_base=core20_124.snap
base_status=try
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "recovery")
c.Check(modeenv.RecoverySystem, Equals, "20191126")
c.Check(modeenv.Base, Equals, "core20_123.snap")
c.Check(modeenv.TryBase, Equals, "core20_124.snap")
c.Check(modeenv.BaseStatus, Equals, boot.TryStatus)
}
func (s *modeenvSuite) TestReadModeWithGrade(c *C) {
s.makeMockModeenvFile(c, `mode=run
grade=dangerous
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "run")
c.Check(modeenv.Grade, Equals, "dangerous")
s.makeMockModeenvFile(c, `mode=run
grade=some-random-grade-string
`)
modeenv, err = boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "run")
c.Check(modeenv.Grade, Equals, "some-random-grade-string")
}
func (s *modeenvSuite) TestReadModeWithModel(c *C) {
tt := []struct {
entry string
model, brand string
}{
{
entry: "my-brand/my-model",
brand: "my-brand",
model: "my-model",
}, {
entry: "my-brand/",
}, {
entry: "my-model/",
}, {
entry: "foobar",
}, {
entry: "/",
}, {
entry: ",",
}, {
entry: "",
},
}
for _, t := range tt {
s.makeMockModeenvFile(c, `mode=run
model=`+t.entry+"\n")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "run")
c.Check(modeenv.Model, Equals, t.model)
c.Check(modeenv.BrandID, Equals, t.brand)
}
}
func (s *modeenvSuite) TestReadModeWithCurrentKernels(c *C) {
tt := []struct {
kernelString string
expectedKernels []string
}{
{
"pc-kernel_1.snap",
[]string{"pc-kernel_1.snap"},
},
{
"pc-kernel_1.snap,pc-kernel_2.snap",
[]string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
},
{
"pc-kernel_1.snap,,,,,pc-kernel_2.snap",
[]string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
},
// we should be robust in parsing the modeenv against garbage
{
`pc-kernel_1.snap,this-is-not-a-real-snap$%^&^%$#@#$%^%"$,pc-kernel_2.snap`,
[]string{"pc-kernel_1.snap", `this-is-not-a-real-snap$%^&^%$#@#$%^%"$`, "pc-kernel_2.snap"},
},
{",,,", nil},
{"", nil},
}
for _, t := range tt {
s.makeMockModeenvFile(c, `mode=recovery
recovery_system=20191126
current_kernels=`+t.kernelString+"\n")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "recovery")
c.Check(modeenv.RecoverySystem, Equals, "20191126")
c.Check(len(modeenv.CurrentKernels), Equals, len(t.expectedKernels))
if len(t.expectedKernels) != 0 {
c.Check(modeenv.CurrentKernels, DeepEquals, t.expectedKernels)
}
}
}
func (s *modeenvSuite) TestWriteToNonExisting(c *C) {
c.Assert(s.mockModeenvPath, testutil.FileAbsent)
modeenv := &boot.Modeenv{Mode: "run"}
err := modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=run\n")
}
func (s *modeenvSuite) TestWriteToExisting(c *C) {
s.makeMockModeenvFile(c, "mode=run")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
modeenv.Mode = "recovery"
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n")
}
func (s *modeenvSuite) TestWriteExisting(c *C) {
s.makeMockModeenvFile(c, "mode=run")
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
modeenv.Mode = "recovery"
err = modeenv.Write()
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n")
}
func (s *modeenvSuite) TestWriteFreshError(c *C) {
modeenv := &boot.Modeenv{Mode: "recovery"}
err := modeenv.Write()
c.Assert(err, ErrorMatches, `internal error: must use WriteTo with modeenv not read from disk`)
}
func (s *modeenvSuite) TestWriteIncompleteModelBrand(c *C) {
modeenv := &boot.Modeenv{
Mode: "run",
Grade: "dangerous",
}
err := modeenv.WriteTo(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: model is unset`)
modeenv.Model = "bar"
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: brand is unset`)
modeenv.BrandID = "foo"
modeenv.TryGrade = "dangerous"
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: try model is unset`)
modeenv.TryModel = "bar"
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: try brand is unset`)
modeenv.TryBrandID = "foo"
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
}
func (s *modeenvSuite) TestWriteToNonExistingFull(c *C) {
c.Assert(s.mockModeenvPath, testutil.FileAbsent)
modeenv := &boot.Modeenv{
Mode: "run",
RecoverySystem: "20191128",
CurrentRecoverySystems: []string{"20191128", "2020-02-03", "20240101-FOO"},
// keep this comment to make gofmt 1.9 happy
Base: "core20_321.snap",
TryBase: "core20_322.snap",
BaseStatus: boot.TryStatus,
CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
}
err := modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run
recovery_system=20191128
current_recovery_systems=20191128,2020-02-03,20240101-FOO
base=core20_321.snap
try_base=core20_322.snap
base_status=try
current_kernels=pc-kernel_1.snap,pc-kernel_2.snap
`)
}
func (s *modeenvSuite) TestReadRecoverySystems(c *C) {
tt := []struct {
systemsString string
expectedSystems []string
}{
{
"20191126",
[]string{"20191126"},
}, {
"20191128,2020-02-03,20240101-FOO",
[]string{"20191128", "2020-02-03", "20240101-FOO"},
},
{",,,", nil},
{"", nil},
}
for _, t := range tt {
c.Logf("tc: %q", t.systemsString)
s.makeMockModeenvFile(c, fmt.Sprintf(`mode=recovery
recovery_system=20191126
current_recovery_systems=%[1]s
good_recovery_systems=%[1]s
`, t.systemsString))
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Mode, Equals, "recovery")
c.Check(modeenv.RecoverySystem, Equals, "20191126")
c.Check(modeenv.CurrentRecoverySystems, DeepEquals, t.expectedSystems)
c.Check(modeenv.GoodRecoverySystems, DeepEquals, t.expectedSystems)
}
}
type fancyDataBothMarshallers struct {
Foo []string
}
func (f *fancyDataBothMarshallers) MarshalModeenvValue() (string, error) {
return strings.Join(f.Foo, "#"), nil
}
func (f *fancyDataBothMarshallers) UnmarshalModeenvValue(v string) error {
f.Foo = strings.Split(v, "#")
return nil
}
func (f *fancyDataBothMarshallers) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("unexpected call to JSON marshaller")
}
func (f *fancyDataBothMarshallers) UnmarshalJSON(data []byte) error {
return fmt.Errorf("unexpected call to JSON unmarshaller")
}
type fancyDataJSONOnly struct {
Foo []string
}
func (f *fancyDataJSONOnly) MarshalJSON() ([]byte, error) {
return json.Marshal(f.Foo)
}
func (f *fancyDataJSONOnly) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &f.Foo)
}
func (s *modeenvSuite) TestFancyMarshalUnmarshal(c *C) {
var buf bytes.Buffer
dboth := fancyDataBothMarshallers{Foo: []string{"1", "two"}}
err := boot.MarshalModeenvEntryTo(&buf, "fancy", &dboth)
c.Assert(err, IsNil)
c.Check(buf.String(), Equals, `fancy=1#two
`)
djson := fancyDataJSONOnly{Foo: []string{"1", "two", "with\nnewline"}}
err = boot.MarshalModeenvEntryTo(&buf, "fancy_json", &djson)
c.Assert(err, IsNil)
c.Check(buf.String(), Equals, `fancy=1#two
fancy_json=["1","two","with\nnewline"]
`)
cfg := goconfigparser.New()
cfg.AllowNoSectionHeader = true
err = cfg.Read(&buf)
c.Assert(err, IsNil)
var dbothRev fancyDataBothMarshallers
err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy", &dbothRev)
c.Assert(err, IsNil)
c.Check(dbothRev, DeepEquals, dboth)
var djsonRev fancyDataJSONOnly
err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev)
c.Assert(err, IsNil)
c.Check(djsonRev, DeepEquals, djson)
}
func (s *modeenvSuite) TestFancyUnmarshalJSONEmpty(c *C) {
var buf bytes.Buffer
cfg := goconfigparser.New()
cfg.AllowNoSectionHeader = true
err := cfg.Read(&buf)
c.Assert(err, IsNil)
var djsonRev fancyDataJSONOnly
err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev)
c.Assert(err, IsNil)
c.Check(djsonRev.Foo, IsNil)
}
func (s *modeenvSuite) TestMarshalCurrentTrustedBootAssets(c *C) {
c.Assert(s.mockModeenvPath, testutil.FileAbsent)
modeenv := &boot.Modeenv{
Mode: "run",
RecoverySystem: "20191128",
CurrentTrustedBootAssets: boot.BootAssetsMap{
"grubx64.efi": []string{"hash1", "hash2"},
},
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
"grubx64.efi": []string{"recovery-hash1"},
"bootx64.efi": []string{"shimhash1", "shimhash2"},
},
}
err := modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run
recovery_system=20191128
current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]}
current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]}
`)
modeenvRead, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(modeenvRead.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
"grubx64.efi": []string{"hash1", "hash2"},
})
c.Assert(modeenvRead.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
"grubx64.efi": []string{"recovery-hash1"},
"bootx64.efi": []string{"shimhash1", "shimhash2"},
})
}
func (s *modeenvSuite) TestMarshalKernelCommandLines(c *C) {
c.Assert(s.mockModeenvPath, testutil.FileAbsent)
modeenv := &boot.Modeenv{
Mode: "run",
RecoverySystem: "20191128",
CurrentKernelCommandLines: boot.BootCommandLines{
`snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`,
`snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`,
},
}
err := modeenv.WriteTo(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run
recovery_system=20191128
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"]
`)
modeenvRead, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Assert(modeenvRead.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
`snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`,
`snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`,
})
}
func (s *modeenvSuite) TestModeenvWithModelGradeSignKeyID(c *C) {
s.makeMockModeenvFile(c, `mode=run
model=canonical/ubuntu-core-20-amd64
grade=dangerous
model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
try_model=developer1/testkeys-snapd-secured-core-20-amd64
try_grade=secured
try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Model, Equals, "ubuntu-core-20-amd64")
c.Check(modeenv.BrandID, Equals, "canonical")
c.Check(modeenv.Classic, Equals, false)
c.Check(modeenv.Grade, Equals, "dangerous")
c.Check(modeenv.ModelSignKeyID, Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
// candidate model
c.Check(modeenv.TryModel, Equals, "testkeys-snapd-secured-core-20-amd64")
c.Check(modeenv.TryBrandID, Equals, "developer1")
c.Check(modeenv.TryGrade, Equals, "secured")
c.Check(modeenv.TryModelSignKeyID, Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
// change some model data now
modeenv.Model = "testkeys-snapd-signed-core-20-amd64"
modeenv.BrandID = "developer1"
modeenv.Grade = "signed"
modeenv.ModelSignKeyID = "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu"
modeenv.TryModel = "bar"
modeenv.TryBrandID = "foo"
modeenv.TryGrade = "dangerous"
modeenv.TryModelSignKeyID = "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn"
// and write it
c.Assert(modeenv.Write(), IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run
model=developer1/testkeys-snapd-signed-core-20-amd64
grade=signed
model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
try_model=foo/bar
try_grade=dangerous
try_model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
`)
}
func (s *modeenvSuite) TestModeenvWithClassicModelGradeSignKeyID(c *C) {
s.makeMockModeenvFile(c, `mode=run
model=canonical/ubuntu-classic-20-amd64
grade=dangerous
classic=true
model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
try_model=developer1/testkeys-snapd-secured-classic-20-amd64
try_grade=secured
try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
c.Check(modeenv.Model, Equals, "ubuntu-classic-20-amd64")
c.Check(modeenv.BrandID, Equals, "canonical")
c.Check(modeenv.Classic, Equals, true)
c.Check(modeenv.Grade, Equals, "dangerous")
c.Check(modeenv.ModelSignKeyID, Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
// candidate model
c.Check(modeenv.TryModel, Equals, "testkeys-snapd-secured-classic-20-amd64")
c.Check(modeenv.TryBrandID, Equals, "developer1")
c.Check(modeenv.TryGrade, Equals, "secured")
c.Check(modeenv.TryModelSignKeyID, Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
// change some model data now
modeenv.Model = "testkeys-snapd-signed-classic-20-amd64"
modeenv.BrandID = "developer1"
modeenv.Grade = "signed"
modeenv.ModelSignKeyID = "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu"
modeenv.TryModel = "bar"
modeenv.TryBrandID = "foo"
modeenv.TryGrade = "dangerous"
modeenv.TryModelSignKeyID = "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn"
// and write it
c.Assert(modeenv.Write(), IsNil)
c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run
model=developer1/testkeys-snapd-signed-classic-20-amd64
classic=true
grade=signed
model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
try_model=foo/bar
try_grade=dangerous
try_model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
`)
}
func (s *modeenvSuite) TestModelForSealing(c *C) {
s.makeMockModeenvFile(c, `mode=run
model=canonical/ubuntu-core-20-amd64
grade=dangerous
model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
try_model=developer1/testkeys-snapd-secured-core-20-amd64
try_grade=secured
try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
modelForSealing := modeenv.ModelForSealing()
c.Check(modelForSealing.Model(), Equals, "ubuntu-core-20-amd64")
c.Check(modelForSealing.BrandID(), Equals, "canonical")
c.Check(modelForSealing.Classic(), Equals, false)
c.Check(modelForSealing.Grade(), Equals, asserts.ModelGrade("dangerous"))
c.Check(modelForSealing.SignKeyID(), Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
c.Check(modelForSealing.Series(), Equals, "16")
c.Check(boot.ModelUniqueID(modelForSealing), Equals,
"canonical/ubuntu-core-20-amd64,dangerous,9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
tryModelForSealing := modeenv.TryModelForSealing()
c.Check(tryModelForSealing.Model(), Equals, "testkeys-snapd-secured-core-20-amd64")
c.Check(tryModelForSealing.BrandID(), Equals, "developer1")
c.Check(tryModelForSealing.Classic(), Equals, false)
c.Check(tryModelForSealing.Grade(), Equals, asserts.ModelGrade("secured"))
c.Check(tryModelForSealing.SignKeyID(), Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
c.Check(tryModelForSealing.Series(), Equals, "16")
c.Check(boot.ModelUniqueID(tryModelForSealing), Equals,
"developer1/testkeys-snapd-secured-core-20-amd64,secured,EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
}
func (s *modeenvSuite) TestClassicModelForSealing(c *C) {
s.makeMockModeenvFile(c, `mode=run
model=canonical/ubuntu-core-20-amd64
classic=true
grade=dangerous
model_sign_key_id=9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
try_model=developer1/testkeys-snapd-secured-core-20-amd64
try_grade=secured
try_model_sign_key_id=EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
`)
modeenv, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, IsNil)
modelForSealing := modeenv.ModelForSealing()
c.Check(modelForSealing.Model(), Equals, "ubuntu-core-20-amd64")
c.Check(modelForSealing.BrandID(), Equals, "canonical")
c.Check(modelForSealing.Classic(), Equals, true)
c.Check(modelForSealing.Grade(), Equals, asserts.ModelGrade("dangerous"))
c.Check(modelForSealing.SignKeyID(), Equals, "9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
c.Check(modelForSealing.Series(), Equals, "16")
c.Check(boot.ModelUniqueID(modelForSealing), Equals,
"canonical/ubuntu-core-20-amd64,dangerous,9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn")
tryModelForSealing := modeenv.TryModelForSealing()
c.Check(tryModelForSealing.Model(), Equals, "testkeys-snapd-secured-core-20-amd64")
c.Check(tryModelForSealing.BrandID(), Equals, "developer1")
c.Check(tryModelForSealing.Classic(), Equals, true)
c.Check(tryModelForSealing.Grade(), Equals, asserts.ModelGrade("secured"))
c.Check(tryModelForSealing.SignKeyID(), Equals, "EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
c.Check(tryModelForSealing.Series(), Equals, "16")
c.Check(boot.ModelUniqueID(tryModelForSealing), Equals,
"developer1/testkeys-snapd-secured-core-20-amd64,secured,EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu")
}
func (s *modeenvSuite) TestModeenvAccessFailsDuringPreseeding(c *C) {
restore := snapdenv.MockPreseeding(true)
defer restore()
_, err := boot.ReadModeenv(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: modeenv cannot be read during preseeding`)
var modeenv boot.Modeenv
err = modeenv.WriteTo(s.tmpdir)
c.Assert(err, ErrorMatches, `internal error: modeenv cannot be written during preseeding`)
}