From 0cef2fd64ef05a82aa4a8214252196d0663b0f66 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 18 Sep 2020 17:48:48 +0200 Subject: [PATCH 01/10] snap-repair: minimal uc20 support This is a minimal change for snap-repair to support uc20. The tests require a rework, ideally we would run all tests for a uc16 and a uc20 model. --- cmd/snap-repair/runner.go | 69 +++++++++-- cmd/snap-repair/runner_test.go | 214 +++++++++++++++++++++++---------- tests/core/basic20/task.yaml | 3 + 3 files changed, 209 insertions(+), 77 deletions(-) diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go index 2a5cd9f4f8b..fcfbc34650c 100644 --- a/cmd/snap-repair/runner.go +++ b/cmd/snap-repair/runner.go @@ -33,6 +33,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strconv" "strings" "syscall" @@ -575,7 +576,7 @@ func (run *Runner) initState() error { os.Remove(dirs.SnapRepairStateFile) run.state = state{} // initialize time lower bound with image built time/seed.yaml time - info, err := os.Stat(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) + info, err := os.Stat(findTimeLowerBoundHintFile()) if err != nil { return err } @@ -655,14 +656,52 @@ func verifySignatures(a asserts.Assertion, workBS asserts.Backstore, trusted ass return nil } -func (run *Runner) initDeviceInfo() error { - const errPrefix = "cannot set device information: " +func findTimeLowerBoundHintFile() string { + // uc20+ + if osutil.FileExists(dirs.SnapModeenvFile) { + return dirs.SnapModeenvFile + } + // uc16,uc18 + return filepath.Join(dirs.SnapSeedDir, "seed.yaml") +} + +func findBrandAndModel() (string, string, error) { + if osutil.FileExists(dirs.SnapModeenvFile) { + return findBrandAndModel20() + } + return findBrandAndModel16() +} + +// XXX: be more precise about brand/model(?) +var modeenvBrandModelRE = regexp.MustCompile(`^model=(.*)/(.*)$`) + +func findBrandAndModel20() (string, string, error) { + // implement own modeenv scanner to ensure are insulated from + // failures in the snapd codebase + // + // XXX: can we trust modeenv enough on unencrypted devices? + f, err := os.Open(dirs.SnapModeenvFile) + if err != nil { + return "", "", err + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + match := modeenvBrandModelRE.FindStringSubmatch(scanner.Text()) + if len(match) > 0 { + return match[1], match[2], nil + } + } + return "", "", fmt.Errorf("cannot find model definition in modeenv") +} + +func findBrandAndModel16() (string, string, error) { workBS := asserts.NewMemoryBackstore() assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) if err != nil { - return err + return "", "", err } var model *asserts.Model for _, fi := range dc { @@ -682,7 +721,7 @@ func (run *Runner) initDeviceInfo() error { switch a.Type() { case asserts.ModelType: if model != nil { - return fmt.Errorf(errPrefix + "multiple models in seed assertions") + return "", "", fmt.Errorf("multiple models in seed assertions") } model = a.(*asserts.Model) case asserts.AccountType, asserts.AccountKeyType: @@ -691,11 +730,11 @@ func (run *Runner) initDeviceInfo() error { } } if model == nil { - return fmt.Errorf(errPrefix + "no model assertion in seed data") + return "", "", fmt.Errorf("no model assertion in seed data") } trustedBS := trustedBackstore(sysdb.Trusted()) if err := verifySignatures(model, workBS, trustedBS); err != nil { - return fmt.Errorf(errPrefix+"%v", err) + return "", "", err } acctPK := []string{model.BrandID()} acctMaxSupFormat := asserts.AccountType.MaxSupportedFormat() @@ -704,14 +743,22 @@ func (run *Runner) initDeviceInfo() error { var err error acct, err = workBS.Get(asserts.AccountType, acctPK, acctMaxSupFormat) if err != nil { - return fmt.Errorf(errPrefix + "no brand account assertion in seed data") + return "", "", fmt.Errorf("no brand account assertion in seed data") } } if err := verifySignatures(acct, workBS, trustedBS); err != nil { - return fmt.Errorf(errPrefix+"%v", err) + return "", "", err + } + return model.BrandID(), model.Model(), nil +} + +func (run *Runner) initDeviceInfo() error { + brandID, model, err := findBrandAndModel() + if err != nil { + return fmt.Errorf("cannot set device information: %v", err) } - run.state.Device.Brand = model.BrandID() - run.state.Device.Model = model.Model() + run.state.Device.Brand = brandID + run.state.Device.Model = model return nil } diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index 6948388bc94..614d2d67499 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -114,22 +114,6 @@ func (s *baseRunnerSuite) SetUpTest(c *C) { s.tmpdir = c.MkDir() dirs.SetRootDir(s.tmpdir) - - s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") - - // dummy seed yaml - err := os.MkdirAll(dirs.SnapSeedDir, 0755) - c.Assert(err, IsNil) - seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") - err = ioutil.WriteFile(seedYamlFn, nil, 0644) - c.Assert(err, IsNil) - seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") - c.Assert(err, IsNil) - err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) - c.Assert(err, IsNil) - s.seedTime = seedTime - - s.t0 = time.Now().UTC().Truncate(time.Minute) } func (s *baseRunnerSuite) TearDownTest(c *C) { @@ -162,22 +146,46 @@ func (s *baseRunnerSuite) freshState(c *C) { c.Assert(err, IsNil) } -type runnerSuite struct { +type baseRunner16Suite struct { baseRunnerSuite +} + +func (s *baseRunner16Suite) SetUpTest(c *C) { + s.baseRunnerSuite.SetUpTest(c) + + s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") + + // dummy seed yaml + err := os.MkdirAll(dirs.SnapSeedDir, 0755) + c.Assert(err, IsNil) + seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") + err = ioutil.WriteFile(seedYamlFn, nil, 0644) + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") + c.Assert(err, IsNil) + err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) + c.Assert(err, IsNil) + s.seedTime = seedTime + + s.t0 = time.Now().UTC().Truncate(time.Minute) +} + +type runner16Suite struct { + baseRunner16Suite restore func() } -func (s *runnerSuite) SetUpSuite(c *C) { +func (s *runner16Suite) SetUpSuite(c *C) { s.baseRunnerSuite.SetUpSuite(c) s.restore = snapdenv.SetUserAgentFromVersion("1", nil, "snap-repair") } -func (s *runnerSuite) TearDownSuite(c *C) { +func (s *runner16Suite) TearDownSuite(c *C) { s.restore() } -var _ = Suite(&runnerSuite{}) +var _ = Suite(&runner16Suite{}) var ( testKey = `type: account-key @@ -228,7 +236,7 @@ func mustParseURL(s string) *url.URL { return u } -func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) { +func (s runner16Suite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) { epoch := time.Unix(0, 0) r := repair.MockTimeNow(func() time.Time { return epoch @@ -237,11 +245,11 @@ func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) ( return r } -func (s *runnerSuite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) { +func (s runner16Suite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) { c.Check(runner.TLSTime().Before(s.t0), Equals, false) } -func (s *runnerSuite) TestFetchJustRepair(c *C) { +func (s runner16Suite) TestFetchJustRepair(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") c.Check(strings.Contains(ua, "snap-repair"), Equals, true) @@ -270,7 +278,7 @@ func (s *runnerSuite) TestFetchJustRepair(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s *runnerSuite) TestFetchScriptTooBig(c *C) { +func (s runner16Suite) TestFetchScriptTooBig(c *C) { restore := repair.MockMaxRepairScriptSize(4) defer restore() @@ -302,7 +310,7 @@ var ( )) ) -func (s *runnerSuite) TestFetch500(c *C) { +func (s runner16Suite) TestFetch500(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -323,7 +331,7 @@ func (s *runnerSuite) TestFetch500(c *C) { c.Assert(n, Equals, 5) } -func (s *runnerSuite) TestFetchEmpty(c *C) { +func (s runner16Suite) TestFetchEmpty(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -344,7 +352,7 @@ func (s *runnerSuite) TestFetchEmpty(c *C) { c.Assert(n, Equals, 5) } -func (s *runnerSuite) TestFetchBroken(c *C) { +func (s runner16Suite) TestFetchBroken(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -366,7 +374,7 @@ func (s *runnerSuite) TestFetchBroken(c *C) { c.Assert(n, Equals, 5) } -func (s *runnerSuite) TestFetchNotFound(c *C) { +func (s runner16Suite) TestFetchNotFound(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -392,7 +400,7 @@ func (s *runnerSuite) TestFetchNotFound(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) { +func (s runner16Suite) TestFetchIfNoneMatchNotModified(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -419,7 +427,7 @@ func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) { +func (s runner16Suite) TestFetchIgnoreSupersededRevision(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, testRepair) })) @@ -434,7 +442,7 @@ func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) { c.Assert(err, Equals, repair.ErrRepairNotModified) } -func (s *runnerSuite) TestFetchIdMismatch(c *C) { +func (s runner16Suite) TestFetchIdMismatch(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") io.WriteString(w, testRepair) @@ -450,7 +458,7 @@ func (s *runnerSuite) TestFetchIdMismatch(c *C) { c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`) } -func (s *runnerSuite) TestFetchWrongFirstType(c *C) { +func (s runner16Suite) TestFetchWrongFirstType(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") @@ -467,7 +475,7 @@ func (s *runnerSuite) TestFetchWrongFirstType(c *C) { c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`) } -func (s *runnerSuite) TestFetchRepairPlusKey(c *C) { +func (s runner16Suite) TestFetchRepairPlusKey(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") @@ -490,7 +498,7 @@ func (s *runnerSuite) TestFetchRepairPlusKey(c *C) { c.Check(ok, Equals, true) } -func (s *runnerSuite) TestPeek(c *C) { +func (s runner16Suite) TestPeek(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") c.Check(strings.Contains(ua, "snap-repair"), Equals, true) @@ -517,7 +525,7 @@ func (s *runnerSuite) TestPeek(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s *runnerSuite) TestPeek500(c *C) { +func (s runner16Suite) TestPeek500(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -538,7 +546,7 @@ func (s *runnerSuite) TestPeek500(c *C) { c.Assert(n, Equals, 5) } -func (s *runnerSuite) TestPeekInvalid(c *C) { +func (s runner16Suite) TestPeekInvalid(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -560,7 +568,7 @@ func (s *runnerSuite) TestPeekInvalid(c *C) { c.Assert(n, Equals, 5) } -func (s *runnerSuite) TestPeekNotFound(c *C) { +func (s runner16Suite) TestPeekNotFound(c *C) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n++ @@ -583,7 +591,7 @@ func (s *runnerSuite) TestPeekNotFound(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s *runnerSuite) TestPeekIdMismatch(c *C) { +func (s runner16Suite) TestPeekIdMismatch(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/json") io.WriteString(w, testHeadersResp) @@ -599,7 +607,7 @@ func (s *runnerSuite) TestPeekIdMismatch(c *C) { c.Assert(err, ErrorMatches, `cannot peek repair headers, repair id mismatch canonical/2 != canonical/4`) } -func (s *runnerSuite) TestLoadState(c *C) { +func (s runner16Suite) TestLoadState(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -610,22 +618,22 @@ func (s *runnerSuite) TestLoadState(c *C) { c.Check(model, Equals, "my-model") } -func (s *runnerSuite) initSeed(c *C) { +func (s runner16Suite) initSeed(c *C) { err := os.MkdirAll(s.seedAssertsDir, 0775) c.Assert(err, IsNil) } -func (s *runnerSuite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { +func (s runner16Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) c.Assert(err, IsNil) } -func (s *runnerSuite) rmSeedAssert(c *C, fname string) { +func (s runner16Suite) rmSeedAssert(c *C, fname string) { err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) c.Assert(err, IsNil) } -func (s *runnerSuite) TestLoadStateInitState(c *C) { +func (s runner16Suite) TestLoadStateInitState(c *C) { // sanity c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) @@ -650,7 +658,7 @@ func (s *runnerSuite) TestLoadStateInitState(c *C) { c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) } -func (s *runnerSuite) TestLoadStateInitDeviceInfoFail(c *C) { +func (s runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { // sanity c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) @@ -690,7 +698,7 @@ func (s *runnerSuite) TestLoadStateInitDeviceInfoFail(c *C) { } } -func (s *runnerSuite) TestTLSTime(c *C) { +func (s runner16Suite) TestTLSTime(c *C) { s.freshState(c) runner := repair.NewRunner() err := runner.LoadState() @@ -718,7 +726,7 @@ func makeReadOnly(c *C, dir string) (restore func()) { } } -func (s *runnerSuite) TestLoadStateInitStateFail(c *C) { +func (s runner16Suite) TestLoadStateInitStateFail(c *C) { restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) defer restore() @@ -727,7 +735,7 @@ func (s *runnerSuite) TestLoadStateInitStateFail(c *C) { c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) } -func (s *runnerSuite) TestSaveStateFail(c *C) { +func (s runner16Suite) TestSaveStateFail(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -748,7 +756,7 @@ func (s *runnerSuite) TestSaveStateFail(c *C) { c.Check(err, ErrorMatches, `cannot save repair state:.*`) } -func (s *runnerSuite) TestSaveState(c *C) { +func (s runner16Suite) TestSaveState(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -767,7 +775,7 @@ func (s *runnerSuite) TestSaveState(c *C) { c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, `{"device":{"brand":"my-brand","model":"my-model"},"sequences":{"canonical":[{"sequence":1,"revision":3,"status":0}]},"time-lower-bound":"2017-08-11T15:49:49Z"}`) } -func (s *runnerSuite) TestApplicable(c *C) { +func (s runner16Suite) TestApplicable(c *C) { s.freshState(c) runner := repair.NewRunner() err := runner.LoadState() @@ -942,14 +950,14 @@ func makeMockServer(c *C, seqRepairs *[]string, redirectFirst bool) *httptest.Se return mockServer } -func (s *runnerSuite) TestTrustedRepairRootKeys(c *C) { +func (s runner16Suite) TestTrustedRepairRootKeys(c *C) { acctKeys := repair.TrustedRepairRootKeys() c.Check(acctKeys, HasLen, 1) c.Check(acctKeys[0].AccountID(), Equals, "canonical") c.Check(acctKeys[0].PublicKeyID(), Equals, "nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t") } -func (s *runnerSuite) TestVerify(c *C) { +func (s runner16Suite) TestVerify(c *C) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -970,7 +978,7 @@ func (s *runnerSuite) TestVerify(c *C) { c.Check(err, IsNil) } -func (s *runnerSuite) signSeqRepairs(c *C, repairs []string) []string { +func (s runner16Suite) signSeqRepairs(c *C, repairs []string) []string { var seq []string for _, rpr := range repairs { decoded, err := asserts.Decode([]byte(rpr)) @@ -986,7 +994,7 @@ func (s *runnerSuite) signSeqRepairs(c *C, repairs []string) []string { return seq } -func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState { +func (s runner16Suite) loadSequences(c *C) map[string][]*repair.RepairState { data, err := ioutil.ReadFile(dirs.SnapRepairStateFile) c.Assert(err, IsNil) var x struct { @@ -997,7 +1005,7 @@ func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState { return x.Sequences } -func (s *runnerSuite) testNext(c *C, redirectFirst bool) { +func (s runner16Suite) testNext(c *C, redirectFirst bool) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -1075,17 +1083,17 @@ func (s *runnerSuite) testNext(c *C, redirectFirst bool) { }) } -func (s *runnerSuite) TestNext(c *C) { +func (s runner16Suite) TestNext(c *C) { redirectFirst := false s.testNext(c, redirectFirst) } -func (s *runnerSuite) TestNextRedirect(c *C) { +func (s runner16Suite) TestNextRedirect(c *C) { redirectFirst := true s.testNext(c, redirectFirst) } -func (s *runnerSuite) TestNextImmediateSkip(c *C) { +func (s runner16Suite) TestNextImmediateSkip(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1122,7 +1130,7 @@ AXNpZw==`} c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s *runnerSuite) TestNextRefetchSkip(c *C) { +func (s runner16Suite) TestNextRefetchSkip(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1200,7 +1208,7 @@ AXNpZw==` c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s *runnerSuite) TestNext500(c *C) { +func (s runner16Suite) TestNext500(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -1219,7 +1227,7 @@ func (s *runnerSuite) TestNext500(c *C) { c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500") } -func (s *runnerSuite) TestNextNotFound(c *C) { +func (s runner16Suite) TestNextNotFound(c *C) { s.freshState(c) restore := repair.MockPeekRetryStrategy(testRetryStrategy) @@ -1249,7 +1257,7 @@ func (s *runnerSuite) TestNextNotFound(c *C) { c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, expected) } -func (s *runnerSuite) TestNextSaveStateError(c *C) { +func (s runner16Suite) TestNextSaveStateError(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1281,7 +1289,7 @@ AXNpZw==`} c.Check(err, ErrorMatches, `cannot save repair state:.*`) } -func (s *runnerSuite) TestNextVerifyNoKey(c *C) { +func (s runner16Suite) TestNextVerifyNoKey(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1309,7 +1317,7 @@ AXNpZw==`} c.Check(runner.Sequence("canonical"), HasLen, 0) } -func (s *runnerSuite) TestNextVerifySelfSigned(c *C) { +func (s runner16Suite) TestNextVerifySelfSigned(c *C) { randoKey, _ := assertstest.GenerateKey(752) randomSigning := assertstest.NewSigningDB("canonical", randoKey) @@ -1351,7 +1359,7 @@ func (s *runnerSuite) TestNextVerifySelfSigned(c *C) { c.Check(runner.Sequence("canonical"), HasLen, 0) } -func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) { +func (s runner16Suite) TestNextVerifyAllKeysOK(c *C) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -1383,7 +1391,7 @@ func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) { c.Check(rpr.RepairID(), Equals, 1) } -func (s *runnerSuite) TestRepairSetStatus(c *C) { +func (s runner16Suite) TestRepairSetStatus(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1426,7 +1434,7 @@ AXNpZw==`} c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s *runnerSuite) TestRepairBasicRun(c *C) { +func (s runner16Suite) TestRepairBasicRun(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1778,3 +1786,77 @@ ls -l ${PATH##*:}/repair c.Assert(err, IsNil) } + +type runner20Suite struct { + baseRunnerSuite +} + +var _ = Suite(&runner20Suite{}) + +var mockModeenv = []byte(` +model=my-brand/my-model-uc20 +`) + +func (s *runner20Suite) SetUpTest(c *C) { + s.baseRunnerSuite.SetUpTest(c) + + s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions") + + // dummy modeenv + err := os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(dirs.SnapModeenvFile, mockModeenv, 0644) + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") + c.Assert(err, IsNil) + err = os.Chtimes(dirs.SnapModeenvFile, seedTime, seedTime) + c.Assert(err, IsNil) + s.seedTime = seedTime + s.t0 = time.Now().UTC().Truncate(time.Minute) +} + +func (s runner20Suite) initSeed(c *C) { + err := os.MkdirAll(s.seedAssertsDir, 0755) + c.Assert(err, IsNil) +} + +func (s runner20Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { + var fn string + if _, ok := a.(*asserts.Model); ok { + fn = filepath.Join(s.seedAssertsDir, "../model") + } else { + fn = filepath.Join(s.seedAssertsDir, fname) + } + + err := ioutil.WriteFile(fn, asserts.Encode(a), 0644) + c.Assert(err, IsNil) +} + +// XXX: this is identical to runner16Suite.TestLoadStateInitState +// except that different "initSeed"/"writeSeedAssert" needs +// calling +func (s *runner20Suite) TestLoadStateInitState(c *C) { + // sanity + c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) + // setup realistic seed/assertions + r := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r() + s.initSeed(c) + // setup realistic seed/assertions + s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) + s.writeSeedAssert(c, "brand.account", s.brandAcct) + s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) + s.writeSeedAssert(c, "model", s.modelAs) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true) + + brand, model := runner.BrandModel() + c.Check(brand, Equals, "my-brand") + c.Check(model, Equals, "my-model-uc20") + + c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) +} diff --git a/tests/core/basic20/task.yaml b/tests/core/basic20/task.yaml index d13d760d56e..f7cff4f53e8 100644 --- a/tests/core/basic20/task.yaml +++ b/tests/core/basic20/task.yaml @@ -67,3 +67,6 @@ execute: | BRAND_ID="$BRAND_ID\*" fi snap recovery --unicode=never | MATCH "[0-9]+ +$BRAND_ID +$MODEL +current" + + echo "snap repair does not fail" + /usr/lib/snapd/snap-repair run From a005243d7adcfb6271bd5306ed73bb7e2cb0582c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 23 Sep 2020 09:23:52 +0200 Subject: [PATCH 02/10] snap-repair: improve tests by checking that we write a valid modeenv Thanks to Ian. --- cmd/snap-repair/runner_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index 614d2d67499..a4e76e259e8 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -41,6 +41,7 @@ import ( "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/assertstest" "github.com/snapcore/snapd/asserts/sysdb" + "github.com/snapcore/snapd/boot" repair "github.com/snapcore/snapd/cmd/snap-repair" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" @@ -1794,6 +1795,7 @@ type runner20Suite struct { var _ = Suite(&runner20Suite{}) var mockModeenv = []byte(` +mode=run model=my-brand/my-model-uc20 `) @@ -1802,11 +1804,15 @@ func (s *runner20Suite) SetUpTest(c *C) { s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions") - // dummy modeenv + // write dummy modeenv err := os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755) c.Assert(err, IsNil) err = ioutil.WriteFile(dirs.SnapModeenvFile, mockModeenv, 0644) c.Assert(err, IsNil) + // validate that modeenv is actually valid + _, err = boot.ReadModeenv("") + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") c.Assert(err, IsNil) err = os.Chtimes(dirs.SnapModeenvFile, seedTime, seedTime) From b3fa9bd9ec6dd4d1f7023aa21de3b32911300427 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 23 Sep 2020 09:50:55 +0200 Subject: [PATCH 03/10] tests: move uc20 snap-repair test into tests/main/snap-repair --- tests/core/basic20/task.yaml | 3 --- tests/main/snap-repair/task.yaml | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/core/basic20/task.yaml b/tests/core/basic20/task.yaml index f7cff4f53e8..d13d760d56e 100644 --- a/tests/core/basic20/task.yaml +++ b/tests/core/basic20/task.yaml @@ -67,6 +67,3 @@ execute: | BRAND_ID="$BRAND_ID\*" fi snap recovery --unicode=never | MATCH "[0-9]+ +$BRAND_ID +$MODEL +current" - - echo "snap repair does not fail" - /usr/lib/snapd/snap-repair run diff --git a/tests/main/snap-repair/task.yaml b/tests/main/snap-repair/task.yaml index 8ed043c1a54..7e414351452 100644 --- a/tests/main/snap-repair/task.yaml +++ b/tests/main/snap-repair/task.yaml @@ -1,8 +1,7 @@ summary: Ensure that snap-repair is available -# TODO:UC20: snap-repair fishes in /var/lib/snapd/seed/assertions for the model -# assertion - this no longer works on UC20 -systems: [-fedora-*, -opensuse-*, -arch-*, -amazon-*, -centos-*, -debian-sid-*, -ubuntu-core-20-*] +# snap-repair is not shipped on non-ubuntu +systems: [-fedora-*, -opensuse-*, -arch-*, -amazon-*, -centos-*, -debian-sid-*] execute: | if ! grep -q "ID=ubuntu-core" /etc/os-release; then From f2f9c240eab9f1568a0dda71aef913af6a519d34 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 23 Sep 2020 10:14:23 +0200 Subject: [PATCH 04/10] snap-reapir: extract uc16/uc18 specific tests into runner16Suite This commit keeps the version independent tests in runnerSuite. It also uses testutil.BaseTest to simplify things. --- cmd/snap-repair/runner_test.go | 394 ++++++++++++++++----------------- 1 file changed, 196 insertions(+), 198 deletions(-) diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index a4e76e259e8..3ab17eb7030 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -51,6 +51,8 @@ import ( ) type baseRunnerSuite struct { + testutil.BaseTest + tmpdir string seedTime time.Time @@ -74,6 +76,21 @@ type baseRunnerSuite struct { restoreLogger func() } +func makeReadOnly(c *C, dir string) (restore func()) { + // skip tests that need this because uid==0 does not honor + // write permissions in directories (yay, unix) + if os.Getuid() == 0 { + // FIXME: we could use osutil.Chattr() here + c.Skip("too lazy to make path readonly as root") + } + err := os.Chmod(dir, 0555) + c.Assert(err, IsNil) + return func() { + err := os.Chmod(dir, 0755) + c.Assert(err, IsNil) + } +} + func (s *baseRunnerSuite) SetUpSuite(c *C) { s.storeSigning = assertstest.NewStoreStack("canonical", nil) @@ -111,15 +128,14 @@ func (s *baseRunnerSuite) SetUpSuite(c *C) { } func (s *baseRunnerSuite) SetUpTest(c *C) { - _, s.restoreLogger = logger.MockLogger() + s.BaseTest.SetUpTest(c) + + _, restoreLogger := logger.MockLogger() + s.AddCleanup(restoreLogger) s.tmpdir = c.MkDir() dirs.SetRootDir(s.tmpdir) -} - -func (s *baseRunnerSuite) TearDownTest(c *C) { - dirs.SetRootDir("/") - s.restoreLogger() + s.AddCleanup(func() { dirs.SetRootDir("/") }) } func (s *baseRunnerSuite) signSeqRepairs(c *C, repairs []string) []string { @@ -147,46 +163,22 @@ func (s *baseRunnerSuite) freshState(c *C) { c.Assert(err, IsNil) } -type baseRunner16Suite struct { +type runnerSuite struct { baseRunnerSuite -} - -func (s *baseRunner16Suite) SetUpTest(c *C) { - s.baseRunnerSuite.SetUpTest(c) - - s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") - - // dummy seed yaml - err := os.MkdirAll(dirs.SnapSeedDir, 0755) - c.Assert(err, IsNil) - seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") - err = ioutil.WriteFile(seedYamlFn, nil, 0644) - c.Assert(err, IsNil) - seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") - c.Assert(err, IsNil) - err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) - c.Assert(err, IsNil) - s.seedTime = seedTime - - s.t0 = time.Now().UTC().Truncate(time.Minute) -} - -type runner16Suite struct { - baseRunner16Suite restore func() } -func (s *runner16Suite) SetUpSuite(c *C) { +func (s *runnerSuite) SetUpSuite(c *C) { s.baseRunnerSuite.SetUpSuite(c) s.restore = snapdenv.SetUserAgentFromVersion("1", nil, "snap-repair") } -func (s *runner16Suite) TearDownSuite(c *C) { +func (s *runnerSuite) TearDownSuite(c *C) { s.restore() } -var _ = Suite(&runner16Suite{}) +var _ = Suite(&runnerSuite{}) var ( testKey = `type: account-key @@ -237,7 +229,7 @@ func mustParseURL(s string) *url.URL { return u } -func (s runner16Suite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) { +func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) { epoch := time.Unix(0, 0) r := repair.MockTimeNow(func() time.Time { return epoch @@ -246,11 +238,11 @@ func (s runner16Suite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) return r } -func (s runner16Suite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) { +func (s *runnerSuite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) { c.Check(runner.TLSTime().Before(s.t0), Equals, false) } -func (s runner16Suite) TestFetchJustRepair(c *C) { +func (s *runnerSuite) TestFetchJustRepair(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") c.Check(strings.Contains(ua, "snap-repair"), Equals, true) @@ -279,7 +271,7 @@ func (s runner16Suite) TestFetchJustRepair(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s runner16Suite) TestFetchScriptTooBig(c *C) { +func (s *runnerSuite) TestFetchScriptTooBig(c *C) { restore := repair.MockMaxRepairScriptSize(4) defer restore() @@ -311,7 +303,7 @@ var ( )) ) -func (s runner16Suite) TestFetch500(c *C) { +func (s *runnerSuite) TestFetch500(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -332,7 +324,7 @@ func (s runner16Suite) TestFetch500(c *C) { c.Assert(n, Equals, 5) } -func (s runner16Suite) TestFetchEmpty(c *C) { +func (s *runnerSuite) TestFetchEmpty(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -353,7 +345,7 @@ func (s runner16Suite) TestFetchEmpty(c *C) { c.Assert(n, Equals, 5) } -func (s runner16Suite) TestFetchBroken(c *C) { +func (s *runnerSuite) TestFetchBroken(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -375,7 +367,7 @@ func (s runner16Suite) TestFetchBroken(c *C) { c.Assert(n, Equals, 5) } -func (s runner16Suite) TestFetchNotFound(c *C) { +func (s *runnerSuite) TestFetchNotFound(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -401,7 +393,7 @@ func (s runner16Suite) TestFetchNotFound(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s runner16Suite) TestFetchIfNoneMatchNotModified(c *C) { +func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) { restore := repair.MockFetchRetryStrategy(testRetryStrategy) defer restore() @@ -428,7 +420,7 @@ func (s runner16Suite) TestFetchIfNoneMatchNotModified(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s runner16Suite) TestFetchIgnoreSupersededRevision(c *C) { +func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, testRepair) })) @@ -443,7 +435,7 @@ func (s runner16Suite) TestFetchIgnoreSupersededRevision(c *C) { c.Assert(err, Equals, repair.ErrRepairNotModified) } -func (s runner16Suite) TestFetchIdMismatch(c *C) { +func (s *runnerSuite) TestFetchIdMismatch(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") io.WriteString(w, testRepair) @@ -459,7 +451,7 @@ func (s runner16Suite) TestFetchIdMismatch(c *C) { c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`) } -func (s runner16Suite) TestFetchWrongFirstType(c *C) { +func (s *runnerSuite) TestFetchWrongFirstType(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") @@ -476,7 +468,7 @@ func (s runner16Suite) TestFetchWrongFirstType(c *C) { c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`) } -func (s runner16Suite) TestFetchRepairPlusKey(c *C) { +func (s *runnerSuite) TestFetchRepairPlusKey(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") @@ -499,7 +491,7 @@ func (s runner16Suite) TestFetchRepairPlusKey(c *C) { c.Check(ok, Equals, true) } -func (s runner16Suite) TestPeek(c *C) { +func (s *runnerSuite) TestPeek(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") c.Check(strings.Contains(ua, "snap-repair"), Equals, true) @@ -526,7 +518,7 @@ func (s runner16Suite) TestPeek(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s runner16Suite) TestPeek500(c *C) { +func (s *runnerSuite) TestPeek500(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -547,7 +539,7 @@ func (s runner16Suite) TestPeek500(c *C) { c.Assert(n, Equals, 5) } -func (s runner16Suite) TestPeekInvalid(c *C) { +func (s *runnerSuite) TestPeekInvalid(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -569,7 +561,7 @@ func (s runner16Suite) TestPeekInvalid(c *C) { c.Assert(n, Equals, 5) } -func (s runner16Suite) TestPeekNotFound(c *C) { +func (s *runnerSuite) TestPeekNotFound(c *C) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n++ @@ -592,7 +584,7 @@ func (s runner16Suite) TestPeekNotFound(c *C) { s.checkBrokenTimeNowMitigated(c, runner) } -func (s runner16Suite) TestPeekIdMismatch(c *C) { +func (s *runnerSuite) TestPeekIdMismatch(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Header.Get("Accept"), Equals, "application/json") io.WriteString(w, testHeadersResp) @@ -608,7 +600,7 @@ func (s runner16Suite) TestPeekIdMismatch(c *C) { c.Assert(err, ErrorMatches, `cannot peek repair headers, repair id mismatch canonical/2 != canonical/4`) } -func (s runner16Suite) TestLoadState(c *C) { +func (s *runnerSuite) TestLoadState(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -619,124 +611,7 @@ func (s runner16Suite) TestLoadState(c *C) { c.Check(model, Equals, "my-model") } -func (s runner16Suite) initSeed(c *C) { - err := os.MkdirAll(s.seedAssertsDir, 0775) - c.Assert(err, IsNil) -} - -func (s runner16Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { - err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) - c.Assert(err, IsNil) -} - -func (s runner16Suite) rmSeedAssert(c *C, fname string) { - err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) - c.Assert(err, IsNil) -} - -func (s runner16Suite) TestLoadStateInitState(c *C) { - // sanity - c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) - c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) - // setup realistic seed/assertions - r := sysdb.InjectTrusted(s.storeSigning.Trusted) - defer r() - s.initSeed(c) - s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) - s.writeSeedAssert(c, "brand.account", s.brandAcct) - s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) - s.writeSeedAssert(c, "model", s.modelAs) - - runner := repair.NewRunner() - err := runner.LoadState() - c.Assert(err, IsNil) - c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true) - - brand, model := runner.BrandModel() - c.Check(brand, Equals, "my-brand") - c.Check(model, Equals, "my-model-2") - - c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) -} - -func (s runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { - // sanity - c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) - c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) - // setup realistic seed/assertions - r := sysdb.InjectTrusted(s.storeSigning.Trusted) - defer r() - s.initSeed(c) - - const errPrefix = "cannot set device information: " - tests := []struct { - breakFunc func() - expectedErr string - }{ - {func() { s.rmSeedAssert(c, "model") }, errPrefix + "no model assertion in seed data"}, - {func() { s.rmSeedAssert(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"}, - {func() { s.rmSeedAssert(c, "brand.account-key") }, errPrefix + `cannot find public key.*`}, - {func() { - // broken signature - blob := asserts.Encode(s.brandAcct) - err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, "brand.account"), blob[:len(blob)-3], 0644) - c.Assert(err, IsNil) - }, errPrefix + "cannot decode signature:.*"}, - {func() { s.writeSeedAssert(c, "model2", s.modelAs) }, errPrefix + "multiple models in seed assertions"}, - } - - for _, test := range tests { - s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) - s.writeSeedAssert(c, "brand.account", s.brandAcct) - s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) - s.writeSeedAssert(c, "model", s.modelAs) - - test.breakFunc() - - runner := repair.NewRunner() - err := runner.LoadState() - c.Check(err, ErrorMatches, test.expectedErr) - } -} - -func (s runner16Suite) TestTLSTime(c *C) { - s.freshState(c) - runner := repair.NewRunner() - err := runner.LoadState() - c.Assert(err, IsNil) - epoch := time.Unix(0, 0) - r := repair.MockTimeNow(func() time.Time { - return epoch - }) - defer r() - c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true) -} - -func makeReadOnly(c *C, dir string) (restore func()) { - // skip tests that need this because uid==0 does not honor - // write permissions in directories (yay, unix) - if os.Getuid() == 0 { - // FIXME: we could use osutil.Chattr() here - c.Skip("too lazy to make path readonly as root") - } - err := os.Chmod(dir, 0555) - c.Assert(err, IsNil) - return func() { - err := os.Chmod(dir, 0755) - c.Assert(err, IsNil) - } -} - -func (s runner16Suite) TestLoadStateInitStateFail(c *C) { - restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) - defer restore() - - runner := repair.NewRunner() - err := runner.LoadState() - c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) -} - -func (s runner16Suite) TestSaveStateFail(c *C) { +func (s *runnerSuite) TestSaveStateFail(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -757,7 +632,7 @@ func (s runner16Suite) TestSaveStateFail(c *C) { c.Check(err, ErrorMatches, `cannot save repair state:.*`) } -func (s runner16Suite) TestSaveState(c *C) { +func (s *runnerSuite) TestSaveState(c *C) { s.freshState(c) runner := repair.NewRunner() @@ -776,7 +651,7 @@ func (s runner16Suite) TestSaveState(c *C) { c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, `{"device":{"brand":"my-brand","model":"my-model"},"sequences":{"canonical":[{"sequence":1,"revision":3,"status":0}]},"time-lower-bound":"2017-08-11T15:49:49Z"}`) } -func (s runner16Suite) TestApplicable(c *C) { +func (s *runnerSuite) TestApplicable(c *C) { s.freshState(c) runner := repair.NewRunner() err := runner.LoadState() @@ -951,14 +826,14 @@ func makeMockServer(c *C, seqRepairs *[]string, redirectFirst bool) *httptest.Se return mockServer } -func (s runner16Suite) TestTrustedRepairRootKeys(c *C) { +func (s *runnerSuite) TestTrustedRepairRootKeys(c *C) { acctKeys := repair.TrustedRepairRootKeys() c.Check(acctKeys, HasLen, 1) c.Check(acctKeys[0].AccountID(), Equals, "canonical") c.Check(acctKeys[0].PublicKeyID(), Equals, "nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t") } -func (s runner16Suite) TestVerify(c *C) { +func (s *runnerSuite) TestVerify(c *C) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -979,7 +854,7 @@ func (s runner16Suite) TestVerify(c *C) { c.Check(err, IsNil) } -func (s runner16Suite) signSeqRepairs(c *C, repairs []string) []string { +func (s *runnerSuite) signSeqRepairs(c *C, repairs []string) []string { var seq []string for _, rpr := range repairs { decoded, err := asserts.Decode([]byte(rpr)) @@ -995,7 +870,7 @@ func (s runner16Suite) signSeqRepairs(c *C, repairs []string) []string { return seq } -func (s runner16Suite) loadSequences(c *C) map[string][]*repair.RepairState { +func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState { data, err := ioutil.ReadFile(dirs.SnapRepairStateFile) c.Assert(err, IsNil) var x struct { @@ -1006,7 +881,7 @@ func (s runner16Suite) loadSequences(c *C) map[string][]*repair.RepairState { return x.Sequences } -func (s runner16Suite) testNext(c *C, redirectFirst bool) { +func (s *runnerSuite) testNext(c *C, redirectFirst bool) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -1084,17 +959,17 @@ func (s runner16Suite) testNext(c *C, redirectFirst bool) { }) } -func (s runner16Suite) TestNext(c *C) { +func (s *runnerSuite) TestNext(c *C) { redirectFirst := false s.testNext(c, redirectFirst) } -func (s runner16Suite) TestNextRedirect(c *C) { +func (s *runnerSuite) TestNextRedirect(c *C) { redirectFirst := true s.testNext(c, redirectFirst) } -func (s runner16Suite) TestNextImmediateSkip(c *C) { +func (s *runnerSuite) TestNextImmediateSkip(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1131,7 +1006,7 @@ AXNpZw==`} c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s runner16Suite) TestNextRefetchSkip(c *C) { +func (s *runnerSuite) TestNextRefetchSkip(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1209,7 +1084,7 @@ AXNpZw==` c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s runner16Suite) TestNext500(c *C) { +func (s *runnerSuite) TestNext500(c *C) { restore := repair.MockPeekRetryStrategy(testRetryStrategy) defer restore() @@ -1228,7 +1103,7 @@ func (s runner16Suite) TestNext500(c *C) { c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500") } -func (s runner16Suite) TestNextNotFound(c *C) { +func (s *runnerSuite) TestNextNotFound(c *C) { s.freshState(c) restore := repair.MockPeekRetryStrategy(testRetryStrategy) @@ -1258,7 +1133,7 @@ func (s runner16Suite) TestNextNotFound(c *C) { c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, expected) } -func (s runner16Suite) TestNextSaveStateError(c *C) { +func (s *runnerSuite) TestNextSaveStateError(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1290,7 +1165,7 @@ AXNpZw==`} c.Check(err, ErrorMatches, `cannot save repair state:.*`) } -func (s runner16Suite) TestNextVerifyNoKey(c *C) { +func (s *runnerSuite) TestNextVerifyNoKey(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1318,7 +1193,7 @@ AXNpZw==`} c.Check(runner.Sequence("canonical"), HasLen, 0) } -func (s runner16Suite) TestNextVerifySelfSigned(c *C) { +func (s *runnerSuite) TestNextVerifySelfSigned(c *C) { randoKey, _ := assertstest.GenerateKey(752) randomSigning := assertstest.NewSigningDB("canonical", randoKey) @@ -1360,7 +1235,7 @@ func (s runner16Suite) TestNextVerifySelfSigned(c *C) { c.Check(runner.Sequence("canonical"), HasLen, 0) } -func (s runner16Suite) TestNextVerifyAllKeysOK(c *C) { +func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) { r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) @@ -1392,7 +1267,7 @@ func (s runner16Suite) TestNextVerifyAllKeysOK(c *C) { c.Check(rpr.RepairID(), Equals, 1) } -func (s runner16Suite) TestRepairSetStatus(c *C) { +func (s *runnerSuite) TestRepairSetStatus(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1435,7 +1310,7 @@ AXNpZw==`} c.Check(seqs["canonical"], DeepEquals, expectedSeq) } -func (s runner16Suite) TestRepairBasicRun(c *C) { +func (s *runnerSuite) TestRepairBasicRun(c *C) { seqRepairs := []string{`type: repair authority-id: canonical brand-id: canonical @@ -1522,6 +1397,7 @@ func (s *runScriptSuite) SetUpTest(c *C) { s.baseRunnerSuite.SetUpTest(c) s.mockServer = makeMockServer(c, &s.seqRepairs, false) + s.AddCleanup(func() { s.mockServer.Close() }) s.runner = repair.NewRunner() s.runner.BaseURL = mustParseURL(s.mockServer.URL) @@ -1529,14 +1405,8 @@ func (s *runScriptSuite) SetUpTest(c *C) { s.runDir = filepath.Join(dirs.SnapRepairRunDir, "canonical", "1") - s.restoreErrTrackerReportRepair = repair.MockErrtrackerReportRepair(s.errtrackerReportRepair) -} - -func (s *runScriptSuite) TearDownTest(c *C) { - s.baseRunnerSuite.TearDownTest(c) - - s.restoreErrTrackerReportRepair() - s.mockServer.Close() + restoreErrTrackerReportRepair := repair.MockErrtrackerReportRepair(s.errtrackerReportRepair) + s.AddCleanup(restoreErrTrackerReportRepair) } func (s *runScriptSuite) errtrackerReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) { @@ -1788,6 +1658,134 @@ ls -l ${PATH##*:}/repair } +type runner16Suite struct { + baseRunnerSuite +} + +var _ = Suite(&runner16Suite{}) + +func (s *runner16Suite) SetUpTest(c *C) { + s.baseRunnerSuite.SetUpTest(c) + + s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") + + // dummy seed yaml + err := os.MkdirAll(dirs.SnapSeedDir, 0755) + c.Assert(err, IsNil) + seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") + err = ioutil.WriteFile(seedYamlFn, nil, 0644) + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") + c.Assert(err, IsNil) + err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) + c.Assert(err, IsNil) + s.seedTime = seedTime + + s.t0 = time.Now().UTC().Truncate(time.Minute) +} + +func (s *runner16Suite) initSeed(c *C) { + err := os.MkdirAll(s.seedAssertsDir, 0775) + c.Assert(err, IsNil) +} + +func (s *runner16Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { + err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) + c.Assert(err, IsNil) +} + +func (s *runner16Suite) rmSeedAssert(c *C, fname string) { + err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) + c.Assert(err, IsNil) +} + +func (s *runner16Suite) TestLoadStateInitStateFail(c *C) { + restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) + defer restore() + + runner := repair.NewRunner() + err := runner.LoadState() + c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) +} + +func (s *runner16Suite) TestTLSTime(c *C) { + s.freshState(c) + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + epoch := time.Unix(0, 0) + r := repair.MockTimeNow(func() time.Time { + return epoch + }) + defer r() + c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true) +} + +func (s *runner16Suite) TestLoadStateInitState(c *C) { + // sanity + c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) + // setup realistic seed/assertions + r := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r() + s.initSeed(c) + s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) + s.writeSeedAssert(c, "brand.account", s.brandAcct) + s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) + s.writeSeedAssert(c, "model", s.modelAs) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true) + + brand, model := runner.BrandModel() + c.Check(brand, Equals, "my-brand") + c.Check(model, Equals, "my-model-2") + + c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) +} + +func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { + // sanity + c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) + // setup realistic seed/assertions + r := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r() + s.initSeed(c) + + const errPrefix = "cannot set device information: " + tests := []struct { + breakFunc func() + expectedErr string + }{ + {func() { s.rmSeedAssert(c, "model") }, errPrefix + "no model assertion in seed data"}, + {func() { s.rmSeedAssert(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"}, + {func() { s.rmSeedAssert(c, "brand.account-key") }, errPrefix + `cannot find public key.*`}, + {func() { + // broken signature + blob := asserts.Encode(s.brandAcct) + err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, "brand.account"), blob[:len(blob)-3], 0644) + c.Assert(err, IsNil) + }, errPrefix + "cannot decode signature:.*"}, + {func() { s.writeSeedAssert(c, "model2", s.modelAs) }, errPrefix + "multiple models in seed assertions"}, + } + + for _, test := range tests { + s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) + s.writeSeedAssert(c, "brand.account", s.brandAcct) + s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) + s.writeSeedAssert(c, "model", s.modelAs) + + test.breakFunc() + + runner := repair.NewRunner() + err := runner.LoadState() + c.Check(err, ErrorMatches, test.expectedErr) + } +} + type runner20Suite struct { baseRunnerSuite } From 4d48d05a08ea58a92799c3086acd08a12dc8b556 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 23 Sep 2020 11:54:01 +0200 Subject: [PATCH 05/10] snap-repair: refactor tests and add uc20 TestLoadStateInitDeviceInfoFail The tests that deal with initState/writeSeedAssert now run with both uc16/uc20 where it makes sense. A new test runner20Suite.TestLoadStateInitDeviceInfoFail that checks the failure conditions for modeenv reading is also added. --- cmd/snap-repair/runner_test.go | 147 ++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index 3ab17eb7030..b23bdd2bd69 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -1658,48 +1658,15 @@ ls -l ${PATH##*:}/repair } -type runner16Suite struct { +type commonRunnerSuite struct { baseRunnerSuite -} - -var _ = Suite(&runner16Suite{}) - -func (s *runner16Suite) SetUpTest(c *C) { - s.baseRunnerSuite.SetUpTest(c) - s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") - - // dummy seed yaml - err := os.MkdirAll(dirs.SnapSeedDir, 0755) - c.Assert(err, IsNil) - seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") - err = ioutil.WriteFile(seedYamlFn, nil, 0644) - c.Assert(err, IsNil) - seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") - c.Assert(err, IsNil) - err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) - c.Assert(err, IsNil) - s.seedTime = seedTime - - s.t0 = time.Now().UTC().Truncate(time.Minute) + initSeed func(c *C) + writeSeedAssert func(c *C, fname string, a asserts.Assertion) + rmSeedAssert func(c *C, fname string) } -func (s *runner16Suite) initSeed(c *C) { - err := os.MkdirAll(s.seedAssertsDir, 0775) - c.Assert(err, IsNil) -} - -func (s *runner16Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { - err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) - c.Assert(err, IsNil) -} - -func (s *runner16Suite) rmSeedAssert(c *C, fname string) { - err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) - c.Assert(err, IsNil) -} - -func (s *runner16Suite) TestLoadStateInitStateFail(c *C) { +func (s *commonRunnerSuite) TestLoadStateInitStateFail(c *C) { restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) defer restore() @@ -1708,7 +1675,7 @@ func (s *runner16Suite) TestLoadStateInitStateFail(c *C) { c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) } -func (s *runner16Suite) TestTLSTime(c *C) { +func (s *commonRunnerSuite) TestTLSTime(c *C) { s.freshState(c) runner := repair.NewRunner() err := runner.LoadState() @@ -1721,7 +1688,7 @@ func (s *runner16Suite) TestTLSTime(c *C) { c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true) } -func (s *runner16Suite) TestLoadStateInitState(c *C) { +func (s *commonRunnerSuite) TestLoadStateInitState(c *C) { // sanity c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) @@ -1746,6 +1713,51 @@ func (s *runner16Suite) TestLoadStateInitState(c *C) { c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) } +type runner16Suite struct { + commonRunnerSuite +} + +var _ = Suite(&runner16Suite{}) + +func (s *runner16Suite) SetUpTest(c *C) { + s.commonRunnerSuite.SetUpTest(c) + + s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") + + // dummy seed yaml + err := os.MkdirAll(dirs.SnapSeedDir, 0755) + c.Assert(err, IsNil) + seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") + err = ioutil.WriteFile(seedYamlFn, nil, 0644) + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") + c.Assert(err, IsNil) + err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) + c.Assert(err, IsNil) + s.seedTime = seedTime + + s.t0 = time.Now().UTC().Truncate(time.Minute) + + s.initSeed = s.initSeed16 + s.writeSeedAssert = s.writeSeedAssert16 + s.rmSeedAssert = s.rmSeedAssert16 +} + +func (s *runner16Suite) initSeed16(c *C) { + err := os.MkdirAll(s.seedAssertsDir, 0775) + c.Assert(err, IsNil) +} + +func (s *runner16Suite) writeSeedAssert16(c *C, fname string, a asserts.Assertion) { + err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) + c.Assert(err, IsNil) +} + +func (s *runner16Suite) rmSeedAssert16(c *C, fname string) { + err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) + c.Assert(err, IsNil) +} + func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { // sanity c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) @@ -1787,18 +1799,18 @@ func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { } type runner20Suite struct { - baseRunnerSuite + commonRunnerSuite } var _ = Suite(&runner20Suite{}) var mockModeenv = []byte(` mode=run -model=my-brand/my-model-uc20 +model=my-brand/my-model-2 `) func (s *runner20Suite) SetUpTest(c *C) { - s.baseRunnerSuite.SetUpTest(c) + s.commonRunnerSuite.SetUpTest(c) s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions") @@ -1817,14 +1829,18 @@ func (s *runner20Suite) SetUpTest(c *C) { c.Assert(err, IsNil) s.seedTime = seedTime s.t0 = time.Now().UTC().Truncate(time.Minute) + + s.initSeed = s.initSeed20 + s.writeSeedAssert = s.writeSeedAssert20 + s.rmSeedAssert = s.rmSeedAssert20 } -func (s runner20Suite) initSeed(c *C) { +func (s *runner20Suite) initSeed20(c *C) { err := os.MkdirAll(s.seedAssertsDir, 0755) c.Assert(err, IsNil) } -func (s runner20Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { +func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertion) { var fn string if _, ok := a.(*asserts.Model); ok { fn = filepath.Join(s.seedAssertsDir, "../model") @@ -1836,31 +1852,26 @@ func (s runner20Suite) writeSeedAssert(c *C, fname string, a asserts.Assertion) c.Assert(err, IsNil) } -// XXX: this is identical to runner16Suite.TestLoadStateInitState -// except that different "initSeed"/"writeSeedAssert" needs -// calling -func (s *runner20Suite) TestLoadStateInitState(c *C) { - // sanity - c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) - c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) - // setup realistic seed/assertions - r := sysdb.InjectTrusted(s.storeSigning.Trusted) - defer r() - s.initSeed(c) - // setup realistic seed/assertions - s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) - s.writeSeedAssert(c, "brand.account", s.brandAcct) - s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) - s.writeSeedAssert(c, "model", s.modelAs) +func (s *runner20Suite) rmSeedAssert20(c *C, fname string) { + panic("not used in uc20 runner tests") +} +func (s *runner20Suite) TestLoadStateInitDeviceInfoFail(c *C) { runner := repair.NewRunner() - err := runner.LoadState() - c.Assert(err, IsNil) - c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true) - brand, model := runner.BrandModel() - c.Check(brand, Equals, "my-brand") - c.Check(model, Equals, "my-model-uc20") + // incorrect modeenv file + err := ioutil.WriteFile(dirs.SnapModeenvFile, []byte(`invalid`), 0644) + c.Assert(err, IsNil) + err = runner.LoadState() + c.Check(err, ErrorMatches, "cannot set device information: cannot find model definition in modeenv") - c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) + // unreadable modeenv file + err = os.Chmod(dirs.SnapModeenvFile, 0300) + c.Assert(err, IsNil) + s.AddCleanup(func() { + err := os.Chmod(dirs.SnapModeenvFile, 0644) + c.Assert(err, IsNil) + }) + err = runner.LoadState() + c.Check(err, ErrorMatches, "cannot set device information: open /.*/modeenv: permission denied") } From 25d4ad209b428a3e9761adb801269ea86c8710e6 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 23 Sep 2020 12:01:43 +0200 Subject: [PATCH 06/10] snap-repair: simplify unit tests some more --- cmd/snap-repair/runner_test.go | 72 ++++++++++++++-------------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index b23bdd2bd69..d13405eb40a 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -611,6 +611,18 @@ func (s *runnerSuite) TestLoadState(c *C) { c.Check(model, Equals, "my-model") } +func (s *runnerSuite) TestLoadStateInitStateFail(c *C) { + err := os.MkdirAll(dirs.SnapSeedDir, 0755) + c.Assert(err, IsNil) + + restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) + defer restore() + + runner := repair.NewRunner() + err = runner.LoadState() + c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) +} + func (s *runnerSuite) TestSaveStateFail(c *C) { s.freshState(c) @@ -1658,24 +1670,16 @@ ls -l ${PATH##*:}/repair } -type commonRunnerSuite struct { +// shared1620RunnerSuite is embedded by runner16Suite and +// runner20Suite and the tests are run once with a simulated uc16 and +// once with a simulated uc20 environment +type shared1620RunnerSuite struct { baseRunnerSuite - initSeed func(c *C) writeSeedAssert func(c *C, fname string, a asserts.Assertion) - rmSeedAssert func(c *C, fname string) -} - -func (s *commonRunnerSuite) TestLoadStateInitStateFail(c *C) { - restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir)) - defer restore() - - runner := repair.NewRunner() - err := runner.LoadState() - c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) } -func (s *commonRunnerSuite) TestTLSTime(c *C) { +func (s *shared1620RunnerSuite) TestTLSTime(c *C) { s.freshState(c) runner := repair.NewRunner() err := runner.LoadState() @@ -1688,14 +1692,13 @@ func (s *commonRunnerSuite) TestTLSTime(c *C) { c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true) } -func (s *commonRunnerSuite) TestLoadStateInitState(c *C) { +func (s *shared1620RunnerSuite) TestLoadStateInitState(c *C) { // sanity c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) // setup realistic seed/assertions r := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r() - s.initSeed(c) s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) s.writeSeedAssert(c, "brand.account", s.brandAcct) s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) @@ -1714,18 +1717,18 @@ func (s *commonRunnerSuite) TestLoadStateInitState(c *C) { } type runner16Suite struct { - commonRunnerSuite + shared1620RunnerSuite } var _ = Suite(&runner16Suite{}) func (s *runner16Suite) SetUpTest(c *C) { - s.commonRunnerSuite.SetUpTest(c) + s.shared1620RunnerSuite.SetUpTest(c) s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") // dummy seed yaml - err := os.MkdirAll(dirs.SnapSeedDir, 0755) + err := os.MkdirAll(s.seedAssertsDir, 0755) c.Assert(err, IsNil) seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") err = ioutil.WriteFile(seedYamlFn, nil, 0644) @@ -1738,14 +1741,7 @@ func (s *runner16Suite) SetUpTest(c *C) { s.t0 = time.Now().UTC().Truncate(time.Minute) - s.initSeed = s.initSeed16 s.writeSeedAssert = s.writeSeedAssert16 - s.rmSeedAssert = s.rmSeedAssert16 -} - -func (s *runner16Suite) initSeed16(c *C) { - err := os.MkdirAll(s.seedAssertsDir, 0775) - c.Assert(err, IsNil) } func (s *runner16Suite) writeSeedAssert16(c *C, fname string, a asserts.Assertion) { @@ -1765,16 +1761,15 @@ func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { // setup realistic seed/assertions r := sysdb.InjectTrusted(s.storeSigning.Trusted) defer r() - s.initSeed(c) const errPrefix = "cannot set device information: " tests := []struct { breakFunc func() expectedErr string }{ - {func() { s.rmSeedAssert(c, "model") }, errPrefix + "no model assertion in seed data"}, - {func() { s.rmSeedAssert(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"}, - {func() { s.rmSeedAssert(c, "brand.account-key") }, errPrefix + `cannot find public key.*`}, + {func() { s.rmSeedAssert16(c, "model") }, errPrefix + "no model assertion in seed data"}, + {func() { s.rmSeedAssert16(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"}, + {func() { s.rmSeedAssert16(c, "brand.account-key") }, errPrefix + `cannot find public key.*`}, {func() { // broken signature blob := asserts.Encode(s.brandAcct) @@ -1799,7 +1794,7 @@ func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) { } type runner20Suite struct { - commonRunnerSuite + shared1620RunnerSuite } var _ = Suite(&runner20Suite{}) @@ -1810,12 +1805,14 @@ model=my-brand/my-model-2 `) func (s *runner20Suite) SetUpTest(c *C) { - s.commonRunnerSuite.SetUpTest(c) + s.shared1620RunnerSuite.SetUpTest(c) s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions") + err := os.MkdirAll(s.seedAssertsDir, 0755) + c.Assert(err, IsNil) // write dummy modeenv - err := os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755) + err = os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755) c.Assert(err, IsNil) err = ioutil.WriteFile(dirs.SnapModeenvFile, mockModeenv, 0644) c.Assert(err, IsNil) @@ -1830,14 +1827,7 @@ func (s *runner20Suite) SetUpTest(c *C) { s.seedTime = seedTime s.t0 = time.Now().UTC().Truncate(time.Minute) - s.initSeed = s.initSeed20 s.writeSeedAssert = s.writeSeedAssert20 - s.rmSeedAssert = s.rmSeedAssert20 -} - -func (s *runner20Suite) initSeed20(c *C) { - err := os.MkdirAll(s.seedAssertsDir, 0755) - c.Assert(err, IsNil) } func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertion) { @@ -1852,10 +1842,6 @@ func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertio c.Assert(err, IsNil) } -func (s *runner20Suite) rmSeedAssert20(c *C, fname string) { - panic("not used in uc20 runner tests") -} - func (s *runner20Suite) TestLoadStateInitDeviceInfoFail(c *C) { runner := repair.NewRunner() From aab25122228ea0c3b943d1f511834f4bc093b50f Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 25 Sep 2020 09:15:34 +0200 Subject: [PATCH 07/10] snap-repair: use goconfigparser to get model from modeenv --- cmd/snap-repair/runner.go | 32 +++++++++++++------------------- cmd/snap-repair/runner_test.go | 34 ++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go index fcfbc34650c..41a15042680 100644 --- a/cmd/snap-repair/runner.go +++ b/cmd/snap-repair/runner.go @@ -33,12 +33,12 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strconv" "strings" "syscall" "time" + "github.com/mvo5/goconfigparser" "gopkg.in/retry.v1" "github.com/snapcore/snapd/arch" @@ -672,28 +672,22 @@ func findBrandAndModel() (string, string, error) { return findBrandAndModel16() } -// XXX: be more precise about brand/model(?) -var modeenvBrandModelRE = regexp.MustCompile(`^model=(.*)/(.*)$`) - -func findBrandAndModel20() (string, string, error) { - // implement own modeenv scanner to ensure are insulated from - // failures in the snapd codebase - // - // XXX: can we trust modeenv enough on unencrypted devices? - f, err := os.Open(dirs.SnapModeenvFile) +func findBrandAndModel20() (brand string, model string, err error) { + cfg := goconfigparser.New() + cfg.AllowNoSectionHeader = true + if err := cfg.ReadFile(dirs.SnapModeenvFile); err != nil { + return "", "", err + } + brandAndModel, err := cfg.Get("", "model") if err != nil { return "", "", err } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - match := modeenvBrandModelRE.FindStringSubmatch(scanner.Text()) - if len(match) > 0 { - return match[1], match[2], nil - } + l := strings.SplitN(brandAndModel, "/", 2) + if len(l) != 2 { + return "", "", fmt.Errorf("cannot find brand/model in modeenv model string %q", brandAndModel) } - return "", "", fmt.Errorf("cannot find model definition in modeenv") + + return l[0], l[1], nil } func findBrandAndModel16() (string, string, error) { diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index d13405eb40a..609125bb500 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -1842,17 +1842,35 @@ func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertio c.Assert(err, IsNil) } -func (s *runner20Suite) TestLoadStateInitDeviceInfoFail(c *C) { +func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvInvalidContent(c *C) { runner := repair.NewRunner() - // incorrect modeenv file - err := ioutil.WriteFile(dirs.SnapModeenvFile, []byte(`invalid`), 0644) - c.Assert(err, IsNil) - err = runner.LoadState() - c.Check(err, ErrorMatches, "cannot set device information: cannot find model definition in modeenv") + for _, tc := range []struct { + modelStr string + expectedErr string + }{ + { + `invalid-key-value`, + "cannot set device information: No option model in section ", + }, { + `model=`, + `cannot set device information: cannot find brand/model in modeenv model string ""`, + }, { + `model=brand-but-no-model`, + `cannot set device information: cannot find brand/model in modeenv model string "brand-but-no-model"`, + }, + } { + err := ioutil.WriteFile(dirs.SnapModeenvFile, []byte(tc.modelStr), 0644) + c.Assert(err, IsNil) + err = runner.LoadState() + c.Check(err, ErrorMatches, tc.expectedErr) + } +} + +func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvIncorrectPermissions(c *C) { + runner := repair.NewRunner() - // unreadable modeenv file - err = os.Chmod(dirs.SnapModeenvFile, 0300) + err := os.Chmod(dirs.SnapModeenvFile, 0300) c.Assert(err, IsNil) s.AddCleanup(func() { err := os.Chmod(dirs.SnapModeenvFile, 0644) From 27a8c1c7203fc8cb2863932773e6fb7084faa03d Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 25 Sep 2020 09:41:32 +0200 Subject: [PATCH 08/10] snap-repair: use systems/*/model as source for findTimeLowerBound() --- cmd/snap-repair/runner.go | 35 +++++++++++++++++++++++++--------- cmd/snap-repair/runner_test.go | 7 ++++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go index 41a15042680..18dc5da0da8 100644 --- a/cmd/snap-repair/runner.go +++ b/cmd/snap-repair/runner.go @@ -576,11 +576,9 @@ func (run *Runner) initState() error { os.Remove(dirs.SnapRepairStateFile) run.state = state{} // initialize time lower bound with image built time/seed.yaml time - info, err := os.Stat(findTimeLowerBoundHintFile()) - if err != nil { + if err := run.findTimeLowerBound(); err != nil { return err } - run.moveTimeLowerBound(info.ModTime()) // initialize device info if err := run.initDeviceInfo(); err != nil { return err @@ -656,13 +654,32 @@ func verifySignatures(a asserts.Assertion, workBS asserts.Backstore, trusted ass return nil } -func findTimeLowerBoundHintFile() string { - // uc20+ - if osutil.FileExists(dirs.SnapModeenvFile) { - return dirs.SnapModeenvFile +func (run *Runner) findTimeLowerBound() error { + timeLowerBoundSources := []string{ + // uc16 + filepath.Join(dirs.SnapSeedDir, "seed.yaml"), + // uc20+ + dirs.SnapModeenvFile, } - // uc16,uc18 - return filepath.Join(dirs.SnapSeedDir, "seed.yaml") + // add all model files from uc20 seeds + allModels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems/*/model")) + if err != nil { + return err + } + timeLowerBoundSources = append(timeLowerBoundSources, allModels...) + + // use all files as potential time inputs + for _, p := range timeLowerBoundSources { + info, err := os.Stat(p) + if os.IsNotExist(err) { + continue + } + if err != nil { + return err + } + run.moveTimeLowerBound(info.ModTime()) + } + return nil } func findBrandAndModel() (string, string, error) { diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go index 609125bb500..3c10e5ba696 100644 --- a/cmd/snap-repair/runner_test.go +++ b/cmd/snap-repair/runner_test.go @@ -1837,9 +1837,14 @@ func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertio } else { fn = filepath.Join(s.seedAssertsDir, fname) } - err := ioutil.WriteFile(fn, asserts.Encode(a), 0644) c.Assert(err, IsNil) + + // ensure model assertion file has the correct seed time + if _, ok := a.(*asserts.Model); ok { + err = os.Chtimes(fn, s.seedTime, s.seedTime) + c.Assert(err, IsNil) + } } func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvInvalidContent(c *C) { From afb5ca1fdf8f5f59a5cae0ecc639f2018528b656 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 28 Sep 2020 15:18:05 +0200 Subject: [PATCH 09/10] snap-repair: tweak findBrandAndModel16 helper Co-authored-by: Maciej Borzecki --- cmd/snap-repair/runner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go index 18dc5da0da8..34e10a2ff6e 100644 --- a/cmd/snap-repair/runner.go +++ b/cmd/snap-repair/runner.go @@ -707,7 +707,7 @@ func findBrandAndModel20() (brand string, model string, err error) { return l[0], l[1], nil } -func findBrandAndModel16() (string, string, error) { +func findBrandAndModel16() (brand, model string, err error) { workBS := asserts.NewMemoryBackstore() assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) From c01b716b3684a6fc770c586cc4c5752c513a791b Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 28 Sep 2020 15:34:09 +0200 Subject: [PATCH 10/10] cmd/snap-repair: tweak model variable name, fix build Signed-off-by: Maciej Borzecki --- cmd/snap-repair/runner.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go index 34e10a2ff6e..d6c13e35e8b 100644 --- a/cmd/snap-repair/runner.go +++ b/cmd/snap-repair/runner.go @@ -689,7 +689,7 @@ func findBrandAndModel() (string, string, error) { return findBrandAndModel16() } -func findBrandAndModel20() (brand string, model string, err error) { +func findBrandAndModel20() (brand, model string, err error) { cfg := goconfigparser.New() cfg.AllowNoSectionHeader = true if err := cfg.ReadFile(dirs.SnapModeenvFile); err != nil { @@ -714,7 +714,7 @@ func findBrandAndModel16() (brand, model string, err error) { if err != nil { return "", "", err } - var model *asserts.Model + var modelAs *asserts.Model for _, fi := range dc { fn := filepath.Join(assertSeedDir, fi.Name()) f, err := os.Open(fn) @@ -731,23 +731,23 @@ func findBrandAndModel16() (brand, model string, err error) { } switch a.Type() { case asserts.ModelType: - if model != nil { + if modelAs != nil { return "", "", fmt.Errorf("multiple models in seed assertions") } - model = a.(*asserts.Model) + modelAs = a.(*asserts.Model) case asserts.AccountType, asserts.AccountKeyType: workBS.Put(a.Type(), a) } } } - if model == nil { + if modelAs == nil { return "", "", fmt.Errorf("no model assertion in seed data") } trustedBS := trustedBackstore(sysdb.Trusted()) - if err := verifySignatures(model, workBS, trustedBS); err != nil { + if err := verifySignatures(modelAs, workBS, trustedBS); err != nil { return "", "", err } - acctPK := []string{model.BrandID()} + acctPK := []string{modelAs.BrandID()} acctMaxSupFormat := asserts.AccountType.MaxSupportedFormat() acct, err := trustedBS.Get(asserts.AccountType, acctPK, acctMaxSupFormat) if err != nil { @@ -760,7 +760,7 @@ func findBrandAndModel16() (brand, model string, err error) { if err := verifySignatures(acct, workBS, trustedBS); err != nil { return "", "", err } - return model.BrandID(), model.Model(), nil + return modelAs.BrandID(), modelAs.Model(), nil } func (run *Runner) initDeviceInfo() error {