github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/cmd/snap-repair/runner_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"net/http"
    29  	"net/http/httptest"
    30  	"net/url"
    31  	"os"
    32  	"path/filepath"
    33  	"strconv"
    34  	"strings"
    35  	"time"
    36  
    37  	. "gopkg.in/check.v1"
    38  	"gopkg.in/retry.v1"
    39  
    40  	"github.com/snapcore/snapd/arch"
    41  	"github.com/snapcore/snapd/asserts"
    42  	"github.com/snapcore/snapd/asserts/assertstest"
    43  	"github.com/snapcore/snapd/asserts/sysdb"
    44  	"github.com/snapcore/snapd/boot"
    45  	repair "github.com/snapcore/snapd/cmd/snap-repair"
    46  	"github.com/snapcore/snapd/dirs"
    47  	"github.com/snapcore/snapd/logger"
    48  	"github.com/snapcore/snapd/osutil"
    49  	"github.com/snapcore/snapd/snapdenv"
    50  	"github.com/snapcore/snapd/testutil"
    51  )
    52  
    53  type baseRunnerSuite struct {
    54  	testutil.BaseTest
    55  
    56  	tmpdir string
    57  
    58  	seedTime time.Time
    59  	t0       time.Time
    60  
    61  	storeSigning *assertstest.StoreStack
    62  
    63  	brandSigning *assertstest.SigningDB
    64  	brandAcct    *asserts.Account
    65  	brandAcctKey *asserts.AccountKey
    66  
    67  	modelAs *asserts.Model
    68  
    69  	seedAssertsDir string
    70  
    71  	repairRootAcctKey *asserts.AccountKey
    72  	repairsAcctKey    *asserts.AccountKey
    73  
    74  	repairsSigning *assertstest.SigningDB
    75  
    76  	restoreLogger func()
    77  }
    78  
    79  func makeReadOnly(c *C, dir string) (restore func()) {
    80  	// skip tests that need this because uid==0 does not honor
    81  	// write permissions in directories (yay, unix)
    82  	if os.Getuid() == 0 {
    83  		// FIXME: we could use osutil.Chattr() here
    84  		c.Skip("too lazy to make path readonly as root")
    85  	}
    86  	err := os.Chmod(dir, 0555)
    87  	c.Assert(err, IsNil)
    88  	return func() {
    89  		err := os.Chmod(dir, 0755)
    90  		c.Assert(err, IsNil)
    91  	}
    92  }
    93  
    94  func (s *baseRunnerSuite) SetUpSuite(c *C) {
    95  	s.storeSigning = assertstest.NewStoreStack("canonical", nil)
    96  
    97  	brandPrivKey, _ := assertstest.GenerateKey(752)
    98  
    99  	s.brandAcct = assertstest.NewAccount(s.storeSigning, "my-brand", map[string]interface{}{
   100  		"account-id": "my-brand",
   101  	}, "")
   102  	s.brandAcctKey = assertstest.NewAccountKey(s.storeSigning, s.brandAcct, nil, brandPrivKey.PublicKey(), "")
   103  	s.brandSigning = assertstest.NewSigningDB("my-brand", brandPrivKey)
   104  
   105  	modelAs, err := s.brandSigning.Sign(asserts.ModelType, map[string]interface{}{
   106  		"series":       "16",
   107  		"brand-id":     "my-brand",
   108  		"model":        "my-model-2",
   109  		"architecture": "armhf",
   110  		"gadget":       "gadget",
   111  		"kernel":       "kernel",
   112  		"timestamp":    time.Now().UTC().Format(time.RFC3339),
   113  	}, nil, "")
   114  	c.Assert(err, IsNil)
   115  	s.modelAs = modelAs.(*asserts.Model)
   116  
   117  	repairRootKey, _ := assertstest.GenerateKey(1024)
   118  
   119  	s.repairRootAcctKey = assertstest.NewAccountKey(s.storeSigning.RootSigning, s.storeSigning.TrustedAccount, nil, repairRootKey.PublicKey(), "")
   120  
   121  	repairsKey, _ := assertstest.GenerateKey(752)
   122  
   123  	repairRootSigning := assertstest.NewSigningDB("canonical", repairRootKey)
   124  
   125  	s.repairsAcctKey = assertstest.NewAccountKey(repairRootSigning, s.storeSigning.TrustedAccount, nil, repairsKey.PublicKey(), "")
   126  
   127  	s.repairsSigning = assertstest.NewSigningDB("canonical", repairsKey)
   128  }
   129  
   130  func (s *baseRunnerSuite) SetUpTest(c *C) {
   131  	s.BaseTest.SetUpTest(c)
   132  
   133  	_, restoreLogger := logger.MockLogger()
   134  	s.AddCleanup(restoreLogger)
   135  
   136  	s.tmpdir = c.MkDir()
   137  	dirs.SetRootDir(s.tmpdir)
   138  	s.AddCleanup(func() { dirs.SetRootDir("/") })
   139  }
   140  
   141  func (s *baseRunnerSuite) signSeqRepairs(c *C, repairs []string) []string {
   142  	var seq []string
   143  	for _, rpr := range repairs {
   144  		decoded, err := asserts.Decode([]byte(rpr))
   145  		c.Assert(err, IsNil)
   146  		signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "")
   147  		c.Assert(err, IsNil)
   148  		buf := &bytes.Buffer{}
   149  		enc := asserts.NewEncoder(buf)
   150  		enc.Encode(signed)
   151  		enc.Encode(s.repairsAcctKey)
   152  		seq = append(seq, buf.String())
   153  	}
   154  	return seq
   155  }
   156  
   157  func checkStateJSON(c *C, file string, exp map[string]interface{}) {
   158  	stateFile := map[string]interface{}{}
   159  	b, err := ioutil.ReadFile(file)
   160  	c.Assert(err, IsNil)
   161  	err = json.Unmarshal(b, &stateFile)
   162  	c.Assert(err, IsNil)
   163  	c.Check(stateFile, DeepEquals, exp)
   164  }
   165  
   166  func (s *baseRunnerSuite) freshState(c *C) {
   167  	// assume base: core18
   168  	s.freshStateWithBaseAndMode(c, "core18", "")
   169  }
   170  
   171  func (s *baseRunnerSuite) freshStateWithBaseAndMode(c *C, base, mode string) {
   172  	err := os.MkdirAll(dirs.SnapRepairDir, 0775)
   173  	c.Assert(err, IsNil)
   174  	stateJSON := map[string]interface{}{
   175  		"device": map[string]string{
   176  			"brand": "my-brand",
   177  			"model": "my-model",
   178  			"base":  base,
   179  			"mode":  mode,
   180  		},
   181  		"time-lower-bound": "2017-08-11T15:49:49Z",
   182  	}
   183  	b, err := json.Marshal(stateJSON)
   184  	c.Assert(err, IsNil)
   185  
   186  	err = ioutil.WriteFile(dirs.SnapRepairStateFile, b, 0600)
   187  	c.Assert(err, IsNil)
   188  }
   189  
   190  type runnerSuite struct {
   191  	baseRunnerSuite
   192  
   193  	restore func()
   194  }
   195  
   196  func (s *runnerSuite) SetUpSuite(c *C) {
   197  	s.baseRunnerSuite.SetUpSuite(c)
   198  	s.restore = snapdenv.SetUserAgentFromVersion("1", nil, "snap-repair")
   199  }
   200  
   201  func (s *runnerSuite) TearDownSuite(c *C) {
   202  	s.restore()
   203  }
   204  
   205  var _ = Suite(&runnerSuite{})
   206  
   207  var (
   208  	testKey = `type: account-key
   209  authority-id: canonical
   210  account-id: canonical
   211  name: repair
   212  public-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   213  since: 2015-11-16T15:04:00Z
   214  body-length: 149
   215  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   216  
   217  AcZrBFaFwYABAvCX5A8dTcdLdhdiuy2YRHO5CAfM5InQefkKOhNMUq2yfi3Sk6trUHxskhZkPnm4
   218  NKx2yRr332q7AJXQHLX+DrZ29ycyoQ2NQGO3eAfQ0hjAAQFYBF8SSh5SutPu5XCVABEBAAE=
   219  
   220  AXNpZw==
   221  `
   222  
   223  	testRepair = `type: repair
   224  authority-id: canonical
   225  brand-id: canonical
   226  repair-id: 2
   227  summary: repair two
   228  architectures:
   229    - amd64
   230    - arm64
   231  series:
   232    - 16
   233  models:
   234    - xyz/frobinator
   235  timestamp: 2017-03-30T12:22:16Z
   236  body-length: 7
   237  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   238  
   239  script
   240  
   241  
   242  AXNpZw==
   243  `
   244  	testHeadersResp = `{"headers":
   245  {"architectures":["amd64","arm64"],"authority-id":"canonical","body-length":"7","brand-id":"canonical","models":["xyz/frobinator"],"repair-id":"2","series":["16"],"sign-key-sha3-384":"KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj","timestamp":"2017-03-30T12:22:16Z","type":"repair"}}`
   246  )
   247  
   248  func mustParseURL(s string) *url.URL {
   249  	u, err := url.Parse(s)
   250  	if err != nil {
   251  		panic(err)
   252  	}
   253  	return u
   254  }
   255  
   256  func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) {
   257  	epoch := time.Unix(0, 0)
   258  	r := repair.MockTimeNow(func() time.Time {
   259  		return epoch
   260  	})
   261  	c.Check(runner.TLSTime().Equal(epoch), Equals, true)
   262  	return r
   263  }
   264  
   265  func (s *runnerSuite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) {
   266  	c.Check(runner.TLSTime().Before(s.t0), Equals, false)
   267  }
   268  
   269  func (s *runnerSuite) TestFetchJustRepair(c *C) {
   270  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   271  		ua := r.Header.Get("User-Agent")
   272  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   273  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   274  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   275  		io.WriteString(w, testRepair)
   276  	}))
   277  
   278  	c.Assert(mockServer, NotNil)
   279  	defer mockServer.Close()
   280  
   281  	runner := repair.NewRunner()
   282  	runner.BaseURL = mustParseURL(mockServer.URL)
   283  
   284  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   285  	defer r()
   286  
   287  	repair, aux, err := runner.Fetch("canonical", 2, -1)
   288  	c.Assert(err, IsNil)
   289  	c.Check(repair, NotNil)
   290  	c.Check(aux, HasLen, 0)
   291  	c.Check(repair.BrandID(), Equals, "canonical")
   292  	c.Check(repair.RepairID(), Equals, 2)
   293  	c.Check(repair.Body(), DeepEquals, []byte("script\n"))
   294  
   295  	s.checkBrokenTimeNowMitigated(c, runner)
   296  }
   297  
   298  func (s *runnerSuite) TestFetchScriptTooBig(c *C) {
   299  	restore := repair.MockMaxRepairScriptSize(4)
   300  	defer restore()
   301  
   302  	n := 0
   303  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   304  		n++
   305  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   306  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   307  		io.WriteString(w, testRepair)
   308  	}))
   309  
   310  	c.Assert(mockServer, NotNil)
   311  	defer mockServer.Close()
   312  
   313  	runner := repair.NewRunner()
   314  	runner.BaseURL = mustParseURL(mockServer.URL)
   315  
   316  	_, _, err := runner.Fetch("canonical", 2, -1)
   317  	c.Assert(err, ErrorMatches, `assertion body length 7 exceeds maximum body size 4 for "repair".*`)
   318  	c.Assert(n, Equals, 1)
   319  }
   320  
   321  var (
   322  	testRetryStrategy = retry.LimitCount(5, retry.LimitTime(1*time.Second,
   323  		retry.Exponential{
   324  			Initial: 1 * time.Millisecond,
   325  			Factor:  1,
   326  		},
   327  	))
   328  )
   329  
   330  func (s *runnerSuite) TestFetch500(c *C) {
   331  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   332  	defer restore()
   333  
   334  	n := 0
   335  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   336  		n++
   337  		w.WriteHeader(500)
   338  	}))
   339  
   340  	c.Assert(mockServer, NotNil)
   341  	defer mockServer.Close()
   342  
   343  	runner := repair.NewRunner()
   344  	runner.BaseURL = mustParseURL(mockServer.URL)
   345  
   346  	_, _, err := runner.Fetch("canonical", 2, -1)
   347  	c.Assert(err, ErrorMatches, "cannot fetch repair, unexpected status 500")
   348  	c.Assert(n, Equals, 5)
   349  }
   350  
   351  func (s *runnerSuite) TestFetchEmpty(c *C) {
   352  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   353  	defer restore()
   354  
   355  	n := 0
   356  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   357  		n++
   358  		w.WriteHeader(200)
   359  	}))
   360  
   361  	c.Assert(mockServer, NotNil)
   362  	defer mockServer.Close()
   363  
   364  	runner := repair.NewRunner()
   365  	runner.BaseURL = mustParseURL(mockServer.URL)
   366  
   367  	_, _, err := runner.Fetch("canonical", 2, -1)
   368  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   369  	c.Assert(n, Equals, 5)
   370  }
   371  
   372  func (s *runnerSuite) TestFetchBroken(c *C) {
   373  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   374  	defer restore()
   375  
   376  	n := 0
   377  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   378  		n++
   379  		w.WriteHeader(200)
   380  		io.WriteString(w, "xyz:")
   381  	}))
   382  
   383  	c.Assert(mockServer, NotNil)
   384  	defer mockServer.Close()
   385  
   386  	runner := repair.NewRunner()
   387  	runner.BaseURL = mustParseURL(mockServer.URL)
   388  
   389  	_, _, err := runner.Fetch("canonical", 2, -1)
   390  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   391  	c.Assert(n, Equals, 5)
   392  }
   393  
   394  func (s *runnerSuite) TestFetchNotFound(c *C) {
   395  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   396  	defer restore()
   397  
   398  	n := 0
   399  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   400  		n++
   401  		w.WriteHeader(404)
   402  	}))
   403  
   404  	c.Assert(mockServer, NotNil)
   405  	defer mockServer.Close()
   406  
   407  	runner := repair.NewRunner()
   408  	runner.BaseURL = mustParseURL(mockServer.URL)
   409  
   410  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   411  	defer r()
   412  
   413  	_, _, err := runner.Fetch("canonical", 2, -1)
   414  	c.Assert(err, Equals, repair.ErrRepairNotFound)
   415  	c.Assert(n, Equals, 1)
   416  
   417  	s.checkBrokenTimeNowMitigated(c, runner)
   418  }
   419  
   420  func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) {
   421  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   422  	defer restore()
   423  
   424  	n := 0
   425  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   426  		n++
   427  		c.Check(r.Header.Get("If-None-Match"), Equals, `"0"`)
   428  		w.WriteHeader(304)
   429  	}))
   430  
   431  	c.Assert(mockServer, NotNil)
   432  	defer mockServer.Close()
   433  
   434  	runner := repair.NewRunner()
   435  	runner.BaseURL = mustParseURL(mockServer.URL)
   436  
   437  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   438  	defer r()
   439  
   440  	_, _, err := runner.Fetch("canonical", 2, 0)
   441  	c.Assert(err, Equals, repair.ErrRepairNotModified)
   442  	c.Assert(n, Equals, 1)
   443  
   444  	s.checkBrokenTimeNowMitigated(c, runner)
   445  }
   446  
   447  func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) {
   448  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   449  		io.WriteString(w, testRepair)
   450  	}))
   451  
   452  	c.Assert(mockServer, NotNil)
   453  	defer mockServer.Close()
   454  
   455  	runner := repair.NewRunner()
   456  	runner.BaseURL = mustParseURL(mockServer.URL)
   457  
   458  	_, _, err := runner.Fetch("canonical", 2, 2)
   459  	c.Assert(err, Equals, repair.ErrRepairNotModified)
   460  }
   461  
   462  func (s *runnerSuite) TestFetchIdMismatch(c *C) {
   463  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   464  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   465  		io.WriteString(w, testRepair)
   466  	}))
   467  
   468  	c.Assert(mockServer, NotNil)
   469  	defer mockServer.Close()
   470  
   471  	runner := repair.NewRunner()
   472  	runner.BaseURL = mustParseURL(mockServer.URL)
   473  
   474  	_, _, err := runner.Fetch("canonical", 4, -1)
   475  	c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`)
   476  }
   477  
   478  func (s *runnerSuite) TestFetchWrongFirstType(c *C) {
   479  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   480  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   481  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   482  		io.WriteString(w, testKey)
   483  	}))
   484  
   485  	c.Assert(mockServer, NotNil)
   486  	defer mockServer.Close()
   487  
   488  	runner := repair.NewRunner()
   489  	runner.BaseURL = mustParseURL(mockServer.URL)
   490  
   491  	_, _, err := runner.Fetch("canonical", 2, -1)
   492  	c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`)
   493  }
   494  
   495  func (s *runnerSuite) TestFetchRepairPlusKey(c *C) {
   496  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   497  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   498  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   499  		io.WriteString(w, testRepair)
   500  		io.WriteString(w, "\n")
   501  		io.WriteString(w, testKey)
   502  	}))
   503  
   504  	c.Assert(mockServer, NotNil)
   505  	defer mockServer.Close()
   506  
   507  	runner := repair.NewRunner()
   508  	runner.BaseURL = mustParseURL(mockServer.URL)
   509  
   510  	repair, aux, err := runner.Fetch("canonical", 2, -1)
   511  	c.Assert(err, IsNil)
   512  	c.Check(repair, NotNil)
   513  	c.Check(aux, HasLen, 1)
   514  	_, ok := aux[0].(*asserts.AccountKey)
   515  	c.Check(ok, Equals, true)
   516  }
   517  
   518  func (s *runnerSuite) TestPeek(c *C) {
   519  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   520  		ua := r.Header.Get("User-Agent")
   521  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   522  		c.Check(r.Header.Get("Accept"), Equals, "application/json")
   523  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   524  		io.WriteString(w, testHeadersResp)
   525  	}))
   526  
   527  	c.Assert(mockServer, NotNil)
   528  	defer mockServer.Close()
   529  
   530  	runner := repair.NewRunner()
   531  	runner.BaseURL = mustParseURL(mockServer.URL)
   532  
   533  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   534  	defer r()
   535  
   536  	h, err := runner.Peek("canonical", 2)
   537  	c.Assert(err, IsNil)
   538  	c.Check(h["series"], DeepEquals, []interface{}{"16"})
   539  	c.Check(h["architectures"], DeepEquals, []interface{}{"amd64", "arm64"})
   540  	c.Check(h["models"], DeepEquals, []interface{}{"xyz/frobinator"})
   541  
   542  	s.checkBrokenTimeNowMitigated(c, runner)
   543  }
   544  
   545  func (s *runnerSuite) TestPeek500(c *C) {
   546  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
   547  	defer restore()
   548  
   549  	n := 0
   550  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   551  		n++
   552  		w.WriteHeader(500)
   553  	}))
   554  
   555  	c.Assert(mockServer, NotNil)
   556  	defer mockServer.Close()
   557  
   558  	runner := repair.NewRunner()
   559  	runner.BaseURL = mustParseURL(mockServer.URL)
   560  
   561  	_, err := runner.Peek("canonical", 2)
   562  	c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500")
   563  	c.Assert(n, Equals, 5)
   564  }
   565  
   566  func (s *runnerSuite) TestPeekInvalid(c *C) {
   567  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
   568  	defer restore()
   569  
   570  	n := 0
   571  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   572  		n++
   573  		w.WriteHeader(200)
   574  		io.WriteString(w, "{")
   575  	}))
   576  
   577  	c.Assert(mockServer, NotNil)
   578  	defer mockServer.Close()
   579  
   580  	runner := repair.NewRunner()
   581  	runner.BaseURL = mustParseURL(mockServer.URL)
   582  
   583  	_, err := runner.Peek("canonical", 2)
   584  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   585  	c.Assert(n, Equals, 5)
   586  }
   587  
   588  func (s *runnerSuite) TestPeekNotFound(c *C) {
   589  	n := 0
   590  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   591  		n++
   592  		w.WriteHeader(404)
   593  	}))
   594  
   595  	c.Assert(mockServer, NotNil)
   596  	defer mockServer.Close()
   597  
   598  	runner := repair.NewRunner()
   599  	runner.BaseURL = mustParseURL(mockServer.URL)
   600  
   601  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   602  	defer r()
   603  
   604  	_, err := runner.Peek("canonical", 2)
   605  	c.Assert(err, Equals, repair.ErrRepairNotFound)
   606  	c.Assert(n, Equals, 1)
   607  
   608  	s.checkBrokenTimeNowMitigated(c, runner)
   609  }
   610  
   611  func (s *runnerSuite) TestPeekIdMismatch(c *C) {
   612  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   613  		c.Check(r.Header.Get("Accept"), Equals, "application/json")
   614  		io.WriteString(w, testHeadersResp)
   615  	}))
   616  
   617  	c.Assert(mockServer, NotNil)
   618  	defer mockServer.Close()
   619  
   620  	runner := repair.NewRunner()
   621  	runner.BaseURL = mustParseURL(mockServer.URL)
   622  
   623  	_, err := runner.Peek("canonical", 4)
   624  	c.Assert(err, ErrorMatches, `cannot peek repair headers, repair id mismatch canonical/2 != canonical/4`)
   625  }
   626  
   627  func (s *runnerSuite) TestLoadState(c *C) {
   628  	s.freshState(c)
   629  
   630  	runner := repair.NewRunner()
   631  	err := runner.LoadState()
   632  	c.Assert(err, IsNil)
   633  	brand, model := runner.BrandModel()
   634  	c.Check(brand, Equals, "my-brand")
   635  	c.Check(model, Equals, "my-model")
   636  }
   637  
   638  func (s *runnerSuite) TestLoadStateInitStateFail(c *C) {
   639  	err := os.MkdirAll(dirs.SnapSeedDir, 0755)
   640  	c.Assert(err, IsNil)
   641  
   642  	restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir))
   643  	defer restore()
   644  
   645  	runner := repair.NewRunner()
   646  	err = runner.LoadState()
   647  	c.Check(err, ErrorMatches, `cannot create repair state directory:.*`)
   648  }
   649  
   650  func (s *runnerSuite) TestSaveStateFail(c *C) {
   651  	s.freshState(c)
   652  
   653  	runner := repair.NewRunner()
   654  	err := runner.LoadState()
   655  	c.Assert(err, IsNil)
   656  
   657  	restore := makeReadOnly(c, dirs.SnapRepairDir)
   658  	defer restore()
   659  
   660  	// no error because this is a no-op
   661  	err = runner.SaveState()
   662  	c.Check(err, IsNil)
   663  
   664  	// mark as modified
   665  	runner.SetStateModified(true)
   666  
   667  	err = runner.SaveState()
   668  	c.Check(err, ErrorMatches, `cannot save repair state:.*`)
   669  }
   670  
   671  func (s *runnerSuite) TestSaveState(c *C) {
   672  	s.freshState(c)
   673  
   674  	runner := repair.NewRunner()
   675  	err := runner.LoadState()
   676  	c.Assert(err, IsNil)
   677  
   678  	runner.SetSequence("canonical", []*repair.RepairState{
   679  		{Sequence: 1, Revision: 3},
   680  	})
   681  	// mark as modified
   682  	runner.SetStateModified(true)
   683  
   684  	err = runner.SaveState()
   685  	c.Assert(err, IsNil)
   686  
   687  	exp := map[string]interface{}{
   688  		"device": map[string]interface{}{
   689  			"brand": "my-brand",
   690  			"model": "my-model",
   691  			"base":  "core18",
   692  			"mode":  "",
   693  		},
   694  		"sequences": map[string]interface{}{
   695  			"canonical": []interface{}{
   696  				map[string]interface{}{
   697  					// all json numbers are floats
   698  					"sequence": 1.0,
   699  					"revision": 3.0,
   700  					"status":   0.0,
   701  				},
   702  			},
   703  		},
   704  		"time-lower-bound": "2017-08-11T15:49:49Z",
   705  	}
   706  
   707  	checkStateJSON(c, dirs.SnapRepairStateFile, exp)
   708  }
   709  
   710  type dev struct {
   711  	base string
   712  	mode string
   713  }
   714  
   715  func (s *runnerSuite) TestApplicable(c *C) {
   716  
   717  	scenarios := []struct {
   718  		device     *dev
   719  		headers    map[string]interface{}
   720  		applicable bool
   721  	}{
   722  		{nil, nil, true},
   723  		{nil, map[string]interface{}{"series": []interface{}{"18"}}, false},
   724  		{nil, map[string]interface{}{"series": []interface{}{"18", "16"}}, true},
   725  		{nil, map[string]interface{}{"series": "18"}, false},
   726  		{nil, map[string]interface{}{"series": []interface{}{18}}, false},
   727  		{nil, map[string]interface{}{"architectures": []interface{}{arch.DpkgArchitecture()}}, true},
   728  		{nil, map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false},
   729  		{nil, map[string]interface{}{"architectures": []interface{}{"other-arch", arch.DpkgArchitecture()}}, true},
   730  		{nil, map[string]interface{}{"architectures": arch.DpkgArchitecture()}, false},
   731  		{nil, map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true},
   732  		{nil, map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false},
   733  		{nil, map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true},
   734  		{nil, map[string]interface{}{"models": "my-brand/my-model"}, false},
   735  		// modes for uc16 / uc18 devices
   736  		{nil, map[string]interface{}{"modes": []interface{}{}}, true},
   737  		{nil, map[string]interface{}{"modes": []interface{}{"run"}}, false},
   738  		{nil, map[string]interface{}{"modes": []interface{}{"recover"}}, false},
   739  		{nil, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, false},
   740  		// run mode for uc20 devices
   741  		{&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{}}, true},
   742  		{&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"run"}}, true},
   743  		{&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"recover"}}, false},
   744  		{&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, true},
   745  		// recover mode for uc20 devices
   746  		{&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{}}, false},
   747  		{&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"run"}}, false},
   748  		{&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"recover"}}, true},
   749  		{&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, true},
   750  		// bases for uc16 devices
   751  		{&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core"}}, true},
   752  		{&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core18"}}, false},
   753  		{&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core", "core18"}}, true},
   754  		// bases for uc18 devices
   755  		{&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core18"}}, true},
   756  		{&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core"}}, false},
   757  		{&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core", "core18"}}, true},
   758  		// bases for uc20 devices
   759  		{&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core20"}}, true},
   760  		{&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core"}}, false},
   761  		{&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core", "core20"}}, true},
   762  		// model prefix matches
   763  		{nil, map[string]interface{}{"models": []interface{}{"my-brand/*"}}, true},
   764  		{nil, map[string]interface{}{"models": []interface{}{"my-brand/my-mod*"}}, true},
   765  		{nil, map[string]interface{}{"models": []interface{}{"my-brand/xxx*"}}, false},
   766  		{nil, map[string]interface{}{"models": []interface{}{"my-brand/my-mod*", "my-brand/xxx*"}}, true},
   767  		{nil, map[string]interface{}{"models": []interface{}{"my*"}}, false},
   768  		{nil, map[string]interface{}{"disabled": "true"}, false},
   769  		{nil, map[string]interface{}{"disabled": "false"}, true},
   770  	}
   771  
   772  	for _, scen := range scenarios {
   773  		if scen.device == nil {
   774  			s.freshState(c)
   775  		} else {
   776  			s.freshStateWithBaseAndMode(c, scen.device.base, scen.device.mode)
   777  		}
   778  
   779  		runner := repair.NewRunner()
   780  		err := runner.LoadState()
   781  		c.Assert(err, IsNil)
   782  
   783  		ok := runner.Applicable(scen.headers)
   784  		c.Check(ok, Equals, scen.applicable, Commentf("%v", scen))
   785  	}
   786  }
   787  
   788  var (
   789  	nextRepairs = []string{`type: repair
   790  authority-id: canonical
   791  brand-id: canonical
   792  repair-id: 1
   793  summary: repair one
   794  timestamp: 2017-07-01T12:00:00Z
   795  body-length: 8
   796  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   797  
   798  scriptA
   799  
   800  
   801  AXNpZw==`,
   802  		`type: repair
   803  authority-id: canonical
   804  brand-id: canonical
   805  repair-id: 2
   806  summary: repair two
   807  series:
   808    - 33
   809  timestamp: 2017-07-02T12:00:00Z
   810  body-length: 8
   811  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   812  
   813  scriptB
   814  
   815  
   816  AXNpZw==`,
   817  		`type: repair
   818  revision: 2
   819  authority-id: canonical
   820  brand-id: canonical
   821  repair-id: 3
   822  summary: repair three rev2
   823  series:
   824    - 16
   825  timestamp: 2017-07-03T12:00:00Z
   826  body-length: 8
   827  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   828  
   829  scriptC
   830  
   831  
   832  AXNpZw==
   833  `}
   834  
   835  	repair3Rev4 = `type: repair
   836  revision: 4
   837  authority-id: canonical
   838  brand-id: canonical
   839  repair-id: 3
   840  summary: repair three rev4
   841  series:
   842    - 16
   843  timestamp: 2017-07-03T12:00:00Z
   844  body-length: 9
   845  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   846  
   847  scriptC2
   848  
   849  
   850  AXNpZw==
   851  `
   852  
   853  	repair4 = `type: repair
   854  authority-id: canonical
   855  brand-id: canonical
   856  repair-id: 4
   857  summary: repair four
   858  timestamp: 2017-07-03T12:00:00Z
   859  body-length: 8
   860  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   861  
   862  scriptD
   863  
   864  
   865  AXNpZw==
   866  `
   867  )
   868  
   869  func makeMockServer(c *C, seqRepairs *[]string, redirectFirst bool) *httptest.Server {
   870  	var mockServer *httptest.Server
   871  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   872  		ua := r.Header.Get("User-Agent")
   873  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   874  
   875  		urlPath := r.URL.Path
   876  		if redirectFirst && r.Header.Get("Accept") == asserts.MediaType {
   877  			if !strings.HasPrefix(urlPath, "/final/") {
   878  				// redirect
   879  				finalURL := mockServer.URL + "/final" + r.URL.Path
   880  				w.Header().Set("Location", finalURL)
   881  				w.WriteHeader(302)
   882  				return
   883  			}
   884  			urlPath = strings.TrimPrefix(urlPath, "/final")
   885  		}
   886  
   887  		c.Check(strings.HasPrefix(urlPath, "/repairs/canonical/"), Equals, true)
   888  
   889  		seq, err := strconv.Atoi(strings.TrimPrefix(urlPath, "/repairs/canonical/"))
   890  		c.Assert(err, IsNil)
   891  
   892  		if seq > len(*seqRepairs) {
   893  			w.WriteHeader(404)
   894  			return
   895  		}
   896  
   897  		rpr := []byte((*seqRepairs)[seq-1])
   898  		dec := asserts.NewDecoder(bytes.NewBuffer(rpr))
   899  		repair, err := dec.Decode()
   900  		c.Assert(err, IsNil)
   901  
   902  		switch r.Header.Get("Accept") {
   903  		case "application/json":
   904  			b, err := json.Marshal(map[string]interface{}{
   905  				"headers": repair.Headers(),
   906  			})
   907  			c.Assert(err, IsNil)
   908  			w.Write(b)
   909  		case asserts.MediaType:
   910  			etag := fmt.Sprintf(`"%d"`, repair.Revision())
   911  			if strings.Contains(r.Header.Get("If-None-Match"), etag) {
   912  				w.WriteHeader(304)
   913  				return
   914  			}
   915  			w.Write(rpr)
   916  		}
   917  	}))
   918  
   919  	c.Assert(mockServer, NotNil)
   920  
   921  	return mockServer
   922  }
   923  
   924  func (s *runnerSuite) TestTrustedRepairRootKeys(c *C) {
   925  	acctKeys := repair.TrustedRepairRootKeys()
   926  	c.Check(acctKeys, HasLen, 1)
   927  	c.Check(acctKeys[0].AccountID(), Equals, "canonical")
   928  	c.Check(acctKeys[0].PublicKeyID(), Equals, "nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t")
   929  }
   930  
   931  func (s *runnerSuite) TestVerify(c *C) {
   932  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
   933  	defer r1()
   934  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
   935  	defer r2()
   936  
   937  	runner := repair.NewRunner()
   938  
   939  	a, err := s.repairsSigning.Sign(asserts.RepairType, map[string]interface{}{
   940  		"brand-id":  "canonical",
   941  		"repair-id": "2",
   942  		"summary":   "repair two",
   943  		"timestamp": time.Now().UTC().Format(time.RFC3339),
   944  	}, []byte("#script"), "")
   945  	c.Assert(err, IsNil)
   946  	rpr := a.(*asserts.Repair)
   947  
   948  	err = runner.Verify(rpr, []asserts.Assertion{s.repairsAcctKey})
   949  	c.Check(err, IsNil)
   950  }
   951  
   952  func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState {
   953  	data, err := ioutil.ReadFile(dirs.SnapRepairStateFile)
   954  	c.Assert(err, IsNil)
   955  	var x struct {
   956  		Sequences map[string][]*repair.RepairState `json:"sequences"`
   957  	}
   958  	err = json.Unmarshal(data, &x)
   959  	c.Assert(err, IsNil)
   960  	return x.Sequences
   961  }
   962  
   963  func (s *runnerSuite) testNext(c *C, redirectFirst bool) {
   964  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
   965  	defer r1()
   966  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
   967  	defer r2()
   968  
   969  	seqRepairs := s.signSeqRepairs(c, nextRepairs)
   970  
   971  	mockServer := makeMockServer(c, &seqRepairs, redirectFirst)
   972  	defer mockServer.Close()
   973  
   974  	runner := repair.NewRunner()
   975  	runner.BaseURL = mustParseURL(mockServer.URL)
   976  	runner.LoadState()
   977  
   978  	rpr, err := runner.Next("canonical")
   979  	c.Assert(err, IsNil)
   980  	c.Check(rpr.RepairID(), Equals, 1)
   981  	c.Check(osutil.FileExists(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "1", "r0.repair")), Equals, true)
   982  
   983  	rpr, err = runner.Next("canonical")
   984  	c.Assert(err, IsNil)
   985  	c.Check(rpr.RepairID(), Equals, 3)
   986  	c.Check(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "3", "r2.repair"), testutil.FileEquals, seqRepairs[2])
   987  
   988  	// no more
   989  	rpr, err = runner.Next("canonical")
   990  	c.Check(err, Equals, repair.ErrRepairNotFound)
   991  
   992  	expectedSeq := []*repair.RepairState{
   993  		{Sequence: 1},
   994  		{Sequence: 2, Status: repair.SkipStatus},
   995  		{Sequence: 3, Revision: 2},
   996  	}
   997  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
   998  	// on disk
   999  	seqs := s.loadSequences(c)
  1000  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1001  
  1002  	// start fresh run with new runner
  1003  	// will refetch repair 3
  1004  	signed := s.signSeqRepairs(c, []string{repair3Rev4, repair4})
  1005  	seqRepairs[2] = signed[0]
  1006  	seqRepairs = append(seqRepairs, signed[1])
  1007  
  1008  	runner = repair.NewRunner()
  1009  	runner.BaseURL = mustParseURL(mockServer.URL)
  1010  	runner.LoadState()
  1011  
  1012  	rpr, err = runner.Next("canonical")
  1013  	c.Assert(err, IsNil)
  1014  	c.Check(rpr.RepairID(), Equals, 1)
  1015  
  1016  	rpr, err = runner.Next("canonical")
  1017  	c.Assert(err, IsNil)
  1018  	c.Check(rpr.RepairID(), Equals, 3)
  1019  	// refetched new revision!
  1020  	c.Check(rpr.Revision(), Equals, 4)
  1021  	c.Check(rpr.Body(), DeepEquals, []byte("scriptC2\n"))
  1022  
  1023  	// new repair
  1024  	rpr, err = runner.Next("canonical")
  1025  	c.Assert(err, IsNil)
  1026  	c.Check(rpr.RepairID(), Equals, 4)
  1027  	c.Check(rpr.Body(), DeepEquals, []byte("scriptD\n"))
  1028  
  1029  	// no more
  1030  	rpr, err = runner.Next("canonical")
  1031  	c.Check(err, Equals, repair.ErrRepairNotFound)
  1032  
  1033  	c.Check(runner.Sequence("canonical"), DeepEquals, []*repair.RepairState{
  1034  		{Sequence: 1},
  1035  		{Sequence: 2, Status: repair.SkipStatus},
  1036  		{Sequence: 3, Revision: 4},
  1037  		{Sequence: 4},
  1038  	})
  1039  }
  1040  
  1041  func (s *runnerSuite) TestNext(c *C) {
  1042  	redirectFirst := false
  1043  	s.testNext(c, redirectFirst)
  1044  }
  1045  
  1046  func (s *runnerSuite) TestNextRedirect(c *C) {
  1047  	redirectFirst := true
  1048  	s.testNext(c, redirectFirst)
  1049  }
  1050  
  1051  func (s *runnerSuite) TestNextImmediateSkip(c *C) {
  1052  	seqRepairs := []string{`type: repair
  1053  authority-id: canonical
  1054  brand-id: canonical
  1055  repair-id: 1
  1056  summary: repair one
  1057  series:
  1058    - 33
  1059  timestamp: 2017-07-02T12:00:00Z
  1060  body-length: 8
  1061  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1062  
  1063  scriptB
  1064  
  1065  
  1066  AXNpZw==`}
  1067  
  1068  	mockServer := makeMockServer(c, &seqRepairs, false)
  1069  	defer mockServer.Close()
  1070  
  1071  	runner := repair.NewRunner()
  1072  	runner.BaseURL = mustParseURL(mockServer.URL)
  1073  	runner.LoadState()
  1074  
  1075  	// not applicable => not returned
  1076  	_, err := runner.Next("canonical")
  1077  	c.Check(err, Equals, repair.ErrRepairNotFound)
  1078  
  1079  	expectedSeq := []*repair.RepairState{
  1080  		{Sequence: 1, Status: repair.SkipStatus},
  1081  	}
  1082  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1083  	// on disk
  1084  	seqs := s.loadSequences(c)
  1085  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1086  }
  1087  
  1088  func (s *runnerSuite) TestNextRefetchSkip(c *C) {
  1089  	seqRepairs := []string{`type: repair
  1090  authority-id: canonical
  1091  brand-id: canonical
  1092  repair-id: 1
  1093  summary: repair one
  1094  series:
  1095    - 16
  1096  timestamp: 2017-07-02T12:00:00Z
  1097  body-length: 8
  1098  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1099  
  1100  scriptB
  1101  
  1102  
  1103  AXNpZw==`}
  1104  
  1105  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1106  	defer r1()
  1107  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1108  	defer r2()
  1109  
  1110  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1111  
  1112  	mockServer := makeMockServer(c, &seqRepairs, false)
  1113  	defer mockServer.Close()
  1114  
  1115  	runner := repair.NewRunner()
  1116  	runner.BaseURL = mustParseURL(mockServer.URL)
  1117  	runner.LoadState()
  1118  
  1119  	_, err := runner.Next("canonical")
  1120  	c.Assert(err, IsNil)
  1121  
  1122  	expectedSeq := []*repair.RepairState{
  1123  		{Sequence: 1},
  1124  	}
  1125  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1126  	// on disk
  1127  	seqs := s.loadSequences(c)
  1128  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1129  
  1130  	// new fresh run, repair becomes now unapplicable
  1131  	seqRepairs[0] = `type: repair
  1132  authority-id: canonical
  1133  revision: 1
  1134  brand-id: canonical
  1135  repair-id: 1
  1136  summary: repair one rev1
  1137  series:
  1138    - 16
  1139  disabled: true
  1140  timestamp: 2017-07-02T12:00:00Z
  1141  body-length: 7
  1142  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1143  
  1144  scriptX
  1145  
  1146  AXNpZw==`
  1147  
  1148  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1149  
  1150  	runner = repair.NewRunner()
  1151  	runner.BaseURL = mustParseURL(mockServer.URL)
  1152  	runner.LoadState()
  1153  
  1154  	_, err = runner.Next("canonical")
  1155  	c.Check(err, Equals, repair.ErrRepairNotFound)
  1156  
  1157  	expectedSeq = []*repair.RepairState{
  1158  		{Sequence: 1, Revision: 1, Status: repair.SkipStatus},
  1159  	}
  1160  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1161  	// on disk
  1162  	seqs = s.loadSequences(c)
  1163  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1164  }
  1165  
  1166  func (s *runnerSuite) TestNext500(c *C) {
  1167  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
  1168  	defer restore()
  1169  
  1170  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1171  		w.WriteHeader(500)
  1172  	}))
  1173  
  1174  	c.Assert(mockServer, NotNil)
  1175  	defer mockServer.Close()
  1176  
  1177  	runner := repair.NewRunner()
  1178  	runner.BaseURL = mustParseURL(mockServer.URL)
  1179  	runner.LoadState()
  1180  
  1181  	_, err := runner.Next("canonical")
  1182  	c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500")
  1183  }
  1184  
  1185  func (s *runnerSuite) TestNextNotFound(c *C) {
  1186  	s.freshState(c)
  1187  
  1188  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
  1189  	defer restore()
  1190  
  1191  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1192  		w.WriteHeader(404)
  1193  	}))
  1194  
  1195  	c.Assert(mockServer, NotNil)
  1196  	defer mockServer.Close()
  1197  
  1198  	runner := repair.NewRunner()
  1199  	runner.BaseURL = mustParseURL(mockServer.URL)
  1200  	runner.LoadState()
  1201  
  1202  	_, err := runner.Next("canonical")
  1203  	c.Assert(err, Equals, repair.ErrRepairNotFound)
  1204  
  1205  	// we saved new time lower bound
  1206  	stateFileExp := map[string]interface{}{
  1207  		"device": map[string]interface{}{
  1208  			"brand": "my-brand",
  1209  			"model": "my-model",
  1210  			"base":  "core18",
  1211  			"mode":  "",
  1212  		},
  1213  		"time-lower-bound": runner.TimeLowerBound().Format(time.RFC3339),
  1214  	}
  1215  
  1216  	checkStateJSON(c, dirs.SnapRepairStateFile, stateFileExp)
  1217  }
  1218  
  1219  func (s *runnerSuite) TestNextSaveStateError(c *C) {
  1220  	seqRepairs := []string{`type: repair
  1221  authority-id: canonical
  1222  brand-id: canonical
  1223  repair-id: 1
  1224  summary: repair one
  1225  series:
  1226    - 33
  1227  timestamp: 2017-07-02T12:00:00Z
  1228  body-length: 8
  1229  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1230  
  1231  scriptB
  1232  
  1233  
  1234  AXNpZw==`}
  1235  
  1236  	mockServer := makeMockServer(c, &seqRepairs, false)
  1237  	defer mockServer.Close()
  1238  
  1239  	runner := repair.NewRunner()
  1240  	runner.BaseURL = mustParseURL(mockServer.URL)
  1241  	runner.LoadState()
  1242  
  1243  	// break SaveState
  1244  	restore := makeReadOnly(c, dirs.SnapRepairDir)
  1245  	defer restore()
  1246  
  1247  	_, err := runner.Next("canonical")
  1248  	c.Check(err, ErrorMatches, `cannot save repair state:.*`)
  1249  }
  1250  
  1251  func (s *runnerSuite) TestNextVerifyNoKey(c *C) {
  1252  	seqRepairs := []string{`type: repair
  1253  authority-id: canonical
  1254  brand-id: canonical
  1255  repair-id: 1
  1256  summary: repair one
  1257  timestamp: 2017-07-02T12:00:00Z
  1258  body-length: 8
  1259  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1260  
  1261  scriptB
  1262  
  1263  
  1264  AXNpZw==`}
  1265  
  1266  	mockServer := makeMockServer(c, &seqRepairs, false)
  1267  	defer mockServer.Close()
  1268  
  1269  	runner := repair.NewRunner()
  1270  	runner.BaseURL = mustParseURL(mockServer.URL)
  1271  	runner.LoadState()
  1272  
  1273  	_, err := runner.Next("canonical")
  1274  	c.Check(err, ErrorMatches, `cannot verify repair canonical-1: cannot find public key.*`)
  1275  
  1276  	c.Check(runner.Sequence("canonical"), HasLen, 0)
  1277  }
  1278  
  1279  func (s *runnerSuite) TestNextVerifySelfSigned(c *C) {
  1280  	randoKey, _ := assertstest.GenerateKey(752)
  1281  
  1282  	randomSigning := assertstest.NewSigningDB("canonical", randoKey)
  1283  	randoKeyEncoded, err := asserts.EncodePublicKey(randoKey.PublicKey())
  1284  	c.Assert(err, IsNil)
  1285  	acctKey, err := randomSigning.Sign(asserts.AccountKeyType, map[string]interface{}{
  1286  		"authority-id":        "canonical",
  1287  		"account-id":          "canonical",
  1288  		"public-key-sha3-384": randoKey.PublicKey().ID(),
  1289  		"name":                "repairs",
  1290  		"since":               time.Now().UTC().Format(time.RFC3339),
  1291  	}, randoKeyEncoded, "")
  1292  	c.Assert(err, IsNil)
  1293  
  1294  	rpr, err := randomSigning.Sign(asserts.RepairType, map[string]interface{}{
  1295  		"brand-id":  "canonical",
  1296  		"repair-id": "1",
  1297  		"summary":   "repair one",
  1298  		"timestamp": time.Now().UTC().Format(time.RFC3339),
  1299  	}, []byte("scriptB\n"), "")
  1300  	c.Assert(err, IsNil)
  1301  
  1302  	buf := &bytes.Buffer{}
  1303  	enc := asserts.NewEncoder(buf)
  1304  	enc.Encode(rpr)
  1305  	enc.Encode(acctKey)
  1306  	seqRepairs := []string{buf.String()}
  1307  
  1308  	mockServer := makeMockServer(c, &seqRepairs, false)
  1309  	defer mockServer.Close()
  1310  
  1311  	runner := repair.NewRunner()
  1312  	runner.BaseURL = mustParseURL(mockServer.URL)
  1313  	runner.LoadState()
  1314  
  1315  	_, err = runner.Next("canonical")
  1316  	c.Check(err, ErrorMatches, `cannot verify repair canonical-1: circular assertions`)
  1317  
  1318  	c.Check(runner.Sequence("canonical"), HasLen, 0)
  1319  }
  1320  
  1321  func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) {
  1322  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1323  	defer r1()
  1324  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1325  	defer r2()
  1326  
  1327  	decoded, err := asserts.Decode([]byte(nextRepairs[0]))
  1328  	c.Assert(err, IsNil)
  1329  	signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "")
  1330  	c.Assert(err, IsNil)
  1331  
  1332  	// stream with all keys (any order) works as well
  1333  	buf := &bytes.Buffer{}
  1334  	enc := asserts.NewEncoder(buf)
  1335  	enc.Encode(signed)
  1336  	enc.Encode(s.storeSigning.TrustedKey)
  1337  	enc.Encode(s.repairRootAcctKey)
  1338  	enc.Encode(s.repairsAcctKey)
  1339  	seqRepairs := []string{buf.String()}
  1340  
  1341  	mockServer := makeMockServer(c, &seqRepairs, false)
  1342  	defer mockServer.Close()
  1343  
  1344  	runner := repair.NewRunner()
  1345  	runner.BaseURL = mustParseURL(mockServer.URL)
  1346  	runner.LoadState()
  1347  
  1348  	rpr, err := runner.Next("canonical")
  1349  	c.Assert(err, IsNil)
  1350  	c.Check(rpr.RepairID(), Equals, 1)
  1351  }
  1352  
  1353  func (s *runnerSuite) TestRepairSetStatus(c *C) {
  1354  	seqRepairs := []string{`type: repair
  1355  authority-id: canonical
  1356  brand-id: canonical
  1357  repair-id: 1
  1358  summary: repair one
  1359  timestamp: 2017-07-02T12:00:00Z
  1360  body-length: 8
  1361  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1362  
  1363  scriptB
  1364  
  1365  
  1366  AXNpZw==`}
  1367  
  1368  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1369  	defer r1()
  1370  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1371  	defer r2()
  1372  
  1373  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1374  
  1375  	mockServer := makeMockServer(c, &seqRepairs, false)
  1376  	defer mockServer.Close()
  1377  
  1378  	runner := repair.NewRunner()
  1379  	runner.BaseURL = mustParseURL(mockServer.URL)
  1380  	runner.LoadState()
  1381  
  1382  	rpr, err := runner.Next("canonical")
  1383  	c.Assert(err, IsNil)
  1384  
  1385  	rpr.SetStatus(repair.DoneStatus)
  1386  
  1387  	expectedSeq := []*repair.RepairState{
  1388  		{Sequence: 1, Status: repair.DoneStatus},
  1389  	}
  1390  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1391  	// on disk
  1392  	seqs := s.loadSequences(c)
  1393  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1394  }
  1395  
  1396  func (s *runnerSuite) TestRepairBasicRun(c *C) {
  1397  	seqRepairs := []string{`type: repair
  1398  authority-id: canonical
  1399  brand-id: canonical
  1400  repair-id: 1
  1401  summary: repair one
  1402  series:
  1403    - 16
  1404  timestamp: 2017-07-02T12:00:00Z
  1405  body-length: 17
  1406  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1407  
  1408  #!/bin/sh
  1409  exit 0
  1410  
  1411  
  1412  AXNpZw==`}
  1413  
  1414  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1415  	defer r1()
  1416  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1417  	defer r2()
  1418  
  1419  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1420  
  1421  	mockServer := makeMockServer(c, &seqRepairs, false)
  1422  	defer mockServer.Close()
  1423  
  1424  	runner := repair.NewRunner()
  1425  	runner.BaseURL = mustParseURL(mockServer.URL)
  1426  	runner.LoadState()
  1427  
  1428  	rpr, err := runner.Next("canonical")
  1429  	c.Assert(err, IsNil)
  1430  
  1431  	err = rpr.Run()
  1432  	c.Assert(err, IsNil)
  1433  	c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, "#!/bin/sh\nexit 0\n")
  1434  }
  1435  
  1436  func (s *runnerSuite) TestRepairBasicRun20RecoverEnv(c *C) {
  1437  	seqRepairs := []string{`type: repair
  1438  authority-id: canonical
  1439  brand-id: canonical
  1440  repair-id: 1
  1441  summary: repair one
  1442  series:
  1443    - 16
  1444  bases:
  1445    - core20
  1446  modes:
  1447    - recover
  1448    - run
  1449  timestamp: 2017-07-02T12:00:00Z
  1450  body-length: 81
  1451  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1452  
  1453  #!/bin/sh
  1454  env | grep SNAP_SYSTEM_MODE
  1455  echo "done" >&$SNAP_REPAIR_STATUS_FD
  1456  exit 0
  1457  
  1458  AXNpZw==`}
  1459  
  1460  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1461  
  1462  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1463  	defer r1()
  1464  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1465  	defer r2()
  1466  
  1467  	for _, mode := range []string{"recover", "run"} {
  1468  		s.freshStateWithBaseAndMode(c, "core20", mode)
  1469  
  1470  		mockServer := makeMockServer(c, &seqRepairs, false)
  1471  		defer mockServer.Close()
  1472  
  1473  		runner := repair.NewRunner()
  1474  		runner.BaseURL = mustParseURL(mockServer.URL)
  1475  		runner.LoadState()
  1476  
  1477  		rpr, err := runner.Next("canonical")
  1478  		c.Assert(err, IsNil)
  1479  
  1480  		err = rpr.Run()
  1481  		c.Assert(err, IsNil)
  1482  		c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, `#!/bin/sh
  1483  env | grep SNAP_SYSTEM_MODE
  1484  echo "done" >&$SNAP_REPAIR_STATUS_FD
  1485  exit 0`)
  1486  
  1487  		c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.done"), testutil.FileEquals, fmt.Sprintf(`repair: canonical-1
  1488  revision: 0
  1489  summary: repair one
  1490  output:
  1491  SNAP_SYSTEM_MODE=%s
  1492  `, mode))
  1493  		// ensure correct permissions
  1494  		fi, err := os.Stat(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.done"))
  1495  		c.Assert(err, IsNil)
  1496  		c.Check(fi.Mode(), Equals, os.FileMode(0600))
  1497  	}
  1498  }
  1499  
  1500  func (s *runnerSuite) TestRepairModesAndBases(c *C) {
  1501  	repairTempl := `type: repair
  1502  authority-id: canonical
  1503  brand-id: canonical
  1504  repair-id: 1
  1505  summary: uc20 recovery repair 
  1506  timestamp: 2017-07-03T12:00:00Z
  1507  body-length: 17
  1508  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1509  %[1]s
  1510  #!/bin/sh
  1511  exit 0
  1512  
  1513  
  1514  AXNpZw==
  1515  	`
  1516  
  1517  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1518  	defer r1()
  1519  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1520  	defer r2()
  1521  
  1522  	tt := []struct {
  1523  		device    *dev
  1524  		modes     []string
  1525  		bases     []string
  1526  		shouldRun bool
  1527  		comment   string
  1528  	}{
  1529  		// uc20 recover mode assertion
  1530  		{
  1531  			&dev{"core20", "recover"},
  1532  			[]string{"recover"},
  1533  			[]string{"core20"},
  1534  			true,
  1535  			"uc20 recover mode w/ uc20 recover mode assertion",
  1536  		},
  1537  		{
  1538  			&dev{"core20", "run"},
  1539  			[]string{"recover"},
  1540  			[]string{"core20"},
  1541  			false,
  1542  			"uc20 run mode w/ uc20 recover mode assertion",
  1543  		},
  1544  		{
  1545  			&dev{base: "core"},
  1546  			[]string{"recover"},
  1547  			[]string{"core20"},
  1548  			false,
  1549  			"uc16 w/ uc20 recover mode assertion",
  1550  		},
  1551  		{
  1552  			&dev{base: "core18"},
  1553  			[]string{"recover"},
  1554  			[]string{"core20"},
  1555  			false,
  1556  			"uc18 w/ uc20 recover mode assertion",
  1557  		},
  1558  
  1559  		// uc20 run mode assertion
  1560  		{
  1561  			&dev{"core20", "recover"},
  1562  			[]string{"run"},
  1563  			[]string{"core20"},
  1564  			false,
  1565  			"uc20 recover mode w/ uc20 run mode assertion",
  1566  		},
  1567  		{
  1568  			&dev{"core20", "run"},
  1569  			[]string{"run"},
  1570  			[]string{"core20"},
  1571  			true,
  1572  			"uc20 run mode w/ uc20 run mode assertion",
  1573  		},
  1574  		{
  1575  			&dev{base: "core"},
  1576  			[]string{"run"},
  1577  			[]string{"core20"},
  1578  			false,
  1579  			"uc16 w/ uc20 run mode assertion",
  1580  		},
  1581  		{
  1582  			&dev{base: "core18"},
  1583  			[]string{"run"},
  1584  			[]string{"core20"},
  1585  			false,
  1586  			"uc18 w/ uc20 run mode assertion",
  1587  		},
  1588  
  1589  		// all uc20 modes assertion
  1590  		{
  1591  			&dev{"core20", "recover"},
  1592  			[]string{"run", "recover"},
  1593  			[]string{"core20"},
  1594  			true,
  1595  			"uc20 recover mode w/ all uc20 modes assertion",
  1596  		},
  1597  		{
  1598  			&dev{"core20", "run"},
  1599  			[]string{"run", "recover"},
  1600  			[]string{"core20"},
  1601  			true,
  1602  			"uc20 run mode w/ all uc20 modes assertion",
  1603  		},
  1604  		{
  1605  			&dev{base: "core"},
  1606  			[]string{"run", "recover"},
  1607  			[]string{"core20"},
  1608  			false,
  1609  			"uc16 w/ all uc20 modes assertion",
  1610  		},
  1611  		{
  1612  			&dev{base: "core18"},
  1613  			[]string{"run", "recover"},
  1614  			[]string{"core20"},
  1615  			false,
  1616  			"uc18 w/ all uc20 modes assertion",
  1617  		},
  1618  
  1619  		// alternate uc20 run mode only assertion
  1620  		{
  1621  			&dev{"core20", "recover"},
  1622  			[]string{},
  1623  			[]string{"core20"},
  1624  			false,
  1625  			"uc20 recover mode w/ alternate uc20 run mode assertion",
  1626  		},
  1627  		{
  1628  			&dev{"core20", "run"},
  1629  			[]string{},
  1630  			[]string{"core20"},
  1631  			true,
  1632  			"uc20 run mode w/ alternate uc20 run mode assertion",
  1633  		},
  1634  		{
  1635  			&dev{base: "core"},
  1636  			[]string{},
  1637  			[]string{"core20"},
  1638  			false,
  1639  			"uc16 w/ alternate uc20 run mode assertion",
  1640  		},
  1641  		{
  1642  			&dev{base: "core18"},
  1643  			[]string{},
  1644  			[]string{"core20"},
  1645  			false,
  1646  			"uc18 w/ alternate uc20 run mode assertion",
  1647  		},
  1648  		{
  1649  			&dev{base: "core"},
  1650  			[]string{"run"},
  1651  			[]string{},
  1652  			false,
  1653  			"uc16 w/ uc20 run mode assertion",
  1654  		},
  1655  		{
  1656  			&dev{base: "core18"},
  1657  			[]string{"run"},
  1658  			[]string{},
  1659  			false,
  1660  			"uc16 w/ uc20 run mode assertion",
  1661  		},
  1662  
  1663  		// all except uc20 recover mode assertion
  1664  		{
  1665  			&dev{"core20", "recover"},
  1666  			[]string{},
  1667  			[]string{},
  1668  			false,
  1669  			"uc20 recover mode w/ all except uc20 recover mode assertion",
  1670  		},
  1671  		{
  1672  			&dev{"core20", "run"},
  1673  			[]string{},
  1674  			[]string{},
  1675  			true,
  1676  			"uc20 run mode w/ all except uc20 recover mode assertion",
  1677  		},
  1678  		{
  1679  			&dev{base: "core"},
  1680  			[]string{},
  1681  			[]string{},
  1682  			true,
  1683  			"uc16 w/ all except uc20 recover mode assertion",
  1684  		},
  1685  		{
  1686  			&dev{base: "core18"},
  1687  			[]string{},
  1688  			[]string{},
  1689  			true,
  1690  			"uc18 w/ all except uc20 recover mode assertion",
  1691  		},
  1692  
  1693  		// uc16 and uc18 assertion
  1694  		{
  1695  			&dev{"core20", "recover"},
  1696  			[]string{},
  1697  			[]string{"core", "core18"},
  1698  			false,
  1699  			"uc20 recover mode w/ uc16 and uc18 assertion",
  1700  		},
  1701  		{
  1702  			&dev{"core20", "run"},
  1703  			[]string{},
  1704  			[]string{"core", "core18"},
  1705  			false,
  1706  			"uc20 run mode w/ uc16 and uc18 assertion",
  1707  		},
  1708  		{
  1709  			&dev{base: "core"},
  1710  			[]string{},
  1711  			[]string{"core", "core18"},
  1712  			true,
  1713  			"uc16 w/ uc16 and uc18 assertion",
  1714  		},
  1715  		{
  1716  			&dev{base: "core18"},
  1717  			[]string{},
  1718  			[]string{"core", "core18"},
  1719  			true,
  1720  			"uc18 w/ uc16 and uc18 assertion",
  1721  		},
  1722  
  1723  		// just uc16 assertion
  1724  		{
  1725  			&dev{"core20", "recover"},
  1726  			[]string{},
  1727  			[]string{"core"},
  1728  			false,
  1729  			"uc20 recover mode w/ just uc16 assertion",
  1730  		},
  1731  		{
  1732  			&dev{"core20", "run"},
  1733  			[]string{},
  1734  			[]string{"core"},
  1735  			false,
  1736  			"uc20 run mode w/ just uc16 assertion",
  1737  		},
  1738  		{
  1739  			&dev{base: "core"},
  1740  			[]string{},
  1741  			[]string{"core"},
  1742  			true,
  1743  			"uc16 w/ just uc16 assertion",
  1744  		},
  1745  		{
  1746  			&dev{base: "core18"},
  1747  			[]string{},
  1748  			[]string{"core"},
  1749  			false,
  1750  			"uc18 w/ just uc16 assertion",
  1751  		},
  1752  
  1753  		// just uc18 assertion
  1754  		{
  1755  			&dev{"core20", "recover"},
  1756  			[]string{},
  1757  			[]string{"core18"},
  1758  			false,
  1759  			"uc20 recover mode w/ just uc18 assertion",
  1760  		},
  1761  		{
  1762  			&dev{"core20", "run"},
  1763  			[]string{},
  1764  			[]string{"core18"},
  1765  			false,
  1766  			"uc20 run mode w/ just uc18 assertion",
  1767  		},
  1768  		{
  1769  			&dev{base: "core"},
  1770  			[]string{},
  1771  			[]string{"core18"},
  1772  			false,
  1773  			"uc16 w/ just uc18 assertion",
  1774  		},
  1775  		{
  1776  			&dev{base: "core18"},
  1777  			[]string{},
  1778  			[]string{"core18"},
  1779  			true,
  1780  			"uc18 w/ just uc18 assertion",
  1781  		},
  1782  	}
  1783  	for _, t := range tt {
  1784  		comment := Commentf(t.comment)
  1785  		cleanups := []func(){}
  1786  
  1787  		// generate the assertion with the bases and modes
  1788  		basesStr := ""
  1789  		if len(t.bases) != 0 {
  1790  			basesStr = "bases:\n"
  1791  			for _, base := range t.bases {
  1792  				basesStr += "  - " + base + "\n"
  1793  			}
  1794  		}
  1795  		modesStr := ""
  1796  		if len(t.modes) != 0 {
  1797  			modesStr = "modes:\n"
  1798  			for _, mode := range t.modes {
  1799  				modesStr += "  - " + mode + "\n"
  1800  			}
  1801  		}
  1802  
  1803  		seqRepairs := s.signSeqRepairs(c, []string{fmt.Sprintf(repairTempl, basesStr+modesStr)})
  1804  
  1805  		mockServer := makeMockServer(c, &seqRepairs, false)
  1806  		cleanups = append(cleanups, func() { mockServer.Close() })
  1807  
  1808  		if t.device == nil {
  1809  			s.freshState(c)
  1810  		} else {
  1811  			s.freshStateWithBaseAndMode(c, t.device.base, t.device.mode)
  1812  		}
  1813  
  1814  		runner := repair.NewRunner()
  1815  		runner.BaseURL = mustParseURL(mockServer.URL)
  1816  		runner.LoadState()
  1817  
  1818  		script := filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script")
  1819  
  1820  		rpr, err := runner.Next("canonical")
  1821  		if t.shouldRun {
  1822  			c.Assert(err, IsNil, comment)
  1823  
  1824  			// run the repair and make sure the script is there
  1825  			err = rpr.Run()
  1826  			c.Assert(err, IsNil, comment)
  1827  			c.Check(script, testutil.FileEquals, "#!/bin/sh\nexit 0\n", comment)
  1828  
  1829  			// remove the script for the next iteration
  1830  			cleanups = append(cleanups, func() { c.Assert(os.RemoveAll(dirs.SnapRepairRunDir), IsNil) })
  1831  		} else {
  1832  			c.Assert(err, Equals, repair.ErrRepairNotFound, comment)
  1833  			c.Check(script, testutil.FileAbsent, comment)
  1834  		}
  1835  
  1836  		for _, r := range cleanups {
  1837  			r()
  1838  		}
  1839  	}
  1840  }
  1841  
  1842  func makeMockRepair(script string) string {
  1843  	return fmt.Sprintf(`type: repair
  1844  authority-id: canonical
  1845  brand-id: canonical
  1846  repair-id: 1
  1847  summary: repair one
  1848  series:
  1849    - 16
  1850  timestamp: 2017-07-02T12:00:00Z
  1851  body-length: %d
  1852  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1853  
  1854  %s
  1855  
  1856  AXNpZw==`, len(script), script)
  1857  }
  1858  
  1859  func verifyRepairStatus(c *C, status repair.RepairStatus) {
  1860  	c.Check(dirs.SnapRepairStateFile, testutil.FileContains, fmt.Sprintf(`{"device":{"brand":"","model":"","base":"","mode":""},"sequences":{"canonical":[{"sequence":1,"revision":0,"status":%d}`, status))
  1861  }
  1862  
  1863  // tests related to correct execution of script
  1864  type runScriptSuite struct {
  1865  	baseRunnerSuite
  1866  
  1867  	seqRepairs []string
  1868  
  1869  	mockServer *httptest.Server
  1870  	runner     *repair.Runner
  1871  
  1872  	runDir string
  1873  
  1874  	restoreErrTrackerReportRepair func()
  1875  	errReport                     struct {
  1876  		repair string
  1877  		errMsg string
  1878  		dupSig string
  1879  		extra  map[string]string
  1880  	}
  1881  }
  1882  
  1883  var _ = Suite(&runScriptSuite{})
  1884  
  1885  func (s *runScriptSuite) SetUpTest(c *C) {
  1886  	s.baseRunnerSuite.SetUpTest(c)
  1887  
  1888  	s.mockServer = makeMockServer(c, &s.seqRepairs, false)
  1889  	s.AddCleanup(func() { s.mockServer.Close() })
  1890  
  1891  	s.runner = repair.NewRunner()
  1892  	s.runner.BaseURL = mustParseURL(s.mockServer.URL)
  1893  	s.runner.LoadState()
  1894  
  1895  	s.runDir = filepath.Join(dirs.SnapRepairRunDir, "canonical", "1")
  1896  
  1897  	restoreErrTrackerReportRepair := repair.MockErrtrackerReportRepair(s.errtrackerReportRepair)
  1898  	s.AddCleanup(restoreErrTrackerReportRepair)
  1899  }
  1900  
  1901  func (s *runScriptSuite) errtrackerReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) {
  1902  	s.errReport.repair = repair
  1903  	s.errReport.errMsg = errMsg
  1904  	s.errReport.dupSig = dupSig
  1905  	s.errReport.extra = extra
  1906  
  1907  	return "some-oops-id", nil
  1908  }
  1909  
  1910  func (s *runScriptSuite) testScriptRun(c *C, mockScript string) *repair.Repair {
  1911  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1912  	defer r1()
  1913  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1914  	defer r2()
  1915  
  1916  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  1917  
  1918  	rpr, err := s.runner.Next("canonical")
  1919  	c.Assert(err, IsNil)
  1920  
  1921  	err = rpr.Run()
  1922  	c.Assert(err, IsNil)
  1923  
  1924  	c.Check(filepath.Join(s.runDir, "r0.script"), testutil.FileEquals, mockScript)
  1925  
  1926  	return rpr
  1927  }
  1928  
  1929  func (s *runScriptSuite) verifyRundir(c *C, names []string) {
  1930  	dirents, err := ioutil.ReadDir(s.runDir)
  1931  	c.Assert(err, IsNil)
  1932  	c.Assert(dirents, HasLen, len(names))
  1933  	for i := range dirents {
  1934  		c.Check(dirents[i].Name(), Matches, names[i])
  1935  	}
  1936  }
  1937  
  1938  type byMtime []os.FileInfo
  1939  
  1940  func (m byMtime) Len() int           { return len(m) }
  1941  func (m byMtime) Less(i, j int) bool { return m[i].ModTime().Before(m[j].ModTime()) }
  1942  func (m byMtime) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
  1943  
  1944  func (s *runScriptSuite) verifyOutput(c *C, name, expectedOutput string) {
  1945  	c.Check(filepath.Join(s.runDir, name), testutil.FileEquals, expectedOutput)
  1946  	// ensure correct permissions
  1947  	fi, err := os.Stat(filepath.Join(s.runDir, name))
  1948  	c.Assert(err, IsNil)
  1949  	c.Check(fi.Mode(), Equals, os.FileMode(0600))
  1950  }
  1951  
  1952  func (s *runScriptSuite) TestRepairBasicRunHappy(c *C) {
  1953  	script := `#!/bin/sh
  1954  echo "happy output"
  1955  echo "done" >&$SNAP_REPAIR_STATUS_FD
  1956  exit 0
  1957  `
  1958  	s.seqRepairs = []string{makeMockRepair(script)}
  1959  	s.testScriptRun(c, script)
  1960  	// verify
  1961  	s.verifyRundir(c, []string{
  1962  		`^r0.done$`,
  1963  		`^r0.script$`,
  1964  		`^work$`,
  1965  	})
  1966  	s.verifyOutput(c, "r0.done", `repair: canonical-1
  1967  revision: 0
  1968  summary: repair one
  1969  output:
  1970  happy output
  1971  `)
  1972  	verifyRepairStatus(c, repair.DoneStatus)
  1973  }
  1974  
  1975  func (s *runScriptSuite) TestRepairBasicRunUnhappy(c *C) {
  1976  	script := `#!/bin/sh
  1977  echo "unhappy output"
  1978  exit 1
  1979  `
  1980  	s.seqRepairs = []string{makeMockRepair(script)}
  1981  	s.testScriptRun(c, script)
  1982  	// verify
  1983  	s.verifyRundir(c, []string{
  1984  		`^r0.retry$`,
  1985  		`^r0.script$`,
  1986  		`^work$`,
  1987  	})
  1988  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  1989  revision: 0
  1990  summary: repair one
  1991  output:
  1992  unhappy output
  1993  
  1994  repair canonical-1 revision 0 failed: exit status 1`)
  1995  	verifyRepairStatus(c, repair.RetryStatus)
  1996  
  1997  	c.Check(s.errReport.repair, Equals, "canonical/1")
  1998  	c.Check(s.errReport.errMsg, Equals, `repair canonical-1 revision 0 failed: exit status 1`)
  1999  	c.Check(s.errReport.dupSig, Equals, `canonical/1
  2000  repair canonical-1 revision 0 failed: exit status 1
  2001  output:
  2002  repair: canonical-1
  2003  revision: 0
  2004  summary: repair one
  2005  output:
  2006  unhappy output
  2007  `)
  2008  	c.Check(s.errReport.extra, DeepEquals, map[string]string{
  2009  		"Revision": "0",
  2010  		"RepairID": "1",
  2011  		"BrandID":  "canonical",
  2012  		"Status":   "retry",
  2013  	})
  2014  }
  2015  
  2016  func (s *runScriptSuite) TestRepairBasicSkip(c *C) {
  2017  	script := `#!/bin/sh
  2018  echo "other output"
  2019  echo "skip" >&$SNAP_REPAIR_STATUS_FD
  2020  exit 0
  2021  `
  2022  	s.seqRepairs = []string{makeMockRepair(script)}
  2023  	s.testScriptRun(c, script)
  2024  	// verify
  2025  	s.verifyRundir(c, []string{
  2026  		`^r0.script$`,
  2027  		`^r0.skip$`,
  2028  		`^work$`,
  2029  	})
  2030  	s.verifyOutput(c, "r0.skip", `repair: canonical-1
  2031  revision: 0
  2032  summary: repair one
  2033  output:
  2034  other output
  2035  `)
  2036  	verifyRepairStatus(c, repair.SkipStatus)
  2037  }
  2038  
  2039  func (s *runScriptSuite) TestRepairBasicRunUnhappyThenHappy(c *C) {
  2040  	script := `#!/bin/sh
  2041  if [ -f zzz-ran-once ]; then
  2042      echo "happy now"
  2043      echo "done" >&$SNAP_REPAIR_STATUS_FD
  2044      exit 0
  2045  fi
  2046  echo "unhappy output"
  2047  touch zzz-ran-once
  2048  exit 1
  2049  `
  2050  	s.seqRepairs = []string{makeMockRepair(script)}
  2051  	rpr := s.testScriptRun(c, script)
  2052  	s.verifyRundir(c, []string{
  2053  		`^r0.retry$`,
  2054  		`^r0.script$`,
  2055  		`^work$`,
  2056  	})
  2057  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  2058  revision: 0
  2059  summary: repair one
  2060  output:
  2061  unhappy output
  2062  
  2063  repair canonical-1 revision 0 failed: exit status 1`)
  2064  	verifyRepairStatus(c, repair.RetryStatus)
  2065  
  2066  	// run again, it will be happy this time
  2067  	err := rpr.Run()
  2068  	c.Assert(err, IsNil)
  2069  
  2070  	s.verifyRundir(c, []string{
  2071  		`^r0.done$`,
  2072  		`^r0.retry$`,
  2073  		`^r0.script$`,
  2074  		`^work$`,
  2075  	})
  2076  	s.verifyOutput(c, "r0.done", `repair: canonical-1
  2077  revision: 0
  2078  summary: repair one
  2079  output:
  2080  happy now
  2081  `)
  2082  	verifyRepairStatus(c, repair.DoneStatus)
  2083  }
  2084  
  2085  func (s *runScriptSuite) TestRepairHitsTimeout(c *C) {
  2086  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  2087  	defer r1()
  2088  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  2089  	defer r2()
  2090  
  2091  	restore := repair.MockDefaultRepairTimeout(100 * time.Millisecond)
  2092  	defer restore()
  2093  
  2094  	script := `#!/bin/sh
  2095  echo "output before timeout"
  2096  sleep 100
  2097  `
  2098  	s.seqRepairs = []string{makeMockRepair(script)}
  2099  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  2100  
  2101  	rpr, err := s.runner.Next("canonical")
  2102  	c.Assert(err, IsNil)
  2103  
  2104  	err = rpr.Run()
  2105  	c.Assert(err, IsNil)
  2106  
  2107  	s.verifyRundir(c, []string{
  2108  		`^r0.retry$`,
  2109  		`^r0.script$`,
  2110  		`^work$`,
  2111  	})
  2112  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  2113  revision: 0
  2114  summary: repair one
  2115  output:
  2116  output before timeout
  2117  
  2118  repair canonical-1 revision 0 failed: repair did not finish within 100ms`)
  2119  	verifyRepairStatus(c, repair.RetryStatus)
  2120  }
  2121  
  2122  func (s *runScriptSuite) TestRepairHasCorrectPath(c *C) {
  2123  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  2124  	defer r1()
  2125  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  2126  	defer r2()
  2127  
  2128  	script := `#!/bin/sh
  2129  echo PATH=$PATH
  2130  ls -l ${PATH##*:}/repair
  2131  `
  2132  	s.seqRepairs = []string{makeMockRepair(script)}
  2133  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  2134  
  2135  	rpr, err := s.runner.Next("canonical")
  2136  	c.Assert(err, IsNil)
  2137  
  2138  	err = rpr.Run()
  2139  	c.Assert(err, IsNil)
  2140  
  2141  	c.Check(filepath.Join(s.runDir, "r0.retry"), testutil.FileMatches, fmt.Sprintf(`(?ms).*^PATH=.*:.*/run/snapd/repair/tools.*`))
  2142  	c.Check(filepath.Join(s.runDir, "r0.retry"), testutil.FileContains, `/repair -> /usr/lib/snapd/snap-repair`)
  2143  
  2144  	// run again and ensure no error happens
  2145  	err = rpr.Run()
  2146  	c.Assert(err, IsNil)
  2147  
  2148  }
  2149  
  2150  // shared1620RunnerSuite is embedded by runner16Suite and
  2151  // runner20Suite and the tests are run once with a simulated uc16 and
  2152  // once with a simulated uc20 environment
  2153  type shared1620RunnerSuite struct {
  2154  	baseRunnerSuite
  2155  
  2156  	writeSeedAssert func(c *C, fname string, a asserts.Assertion)
  2157  
  2158  	// this is so we can check device details that will be different in the
  2159  	// 20 version of tests from the 16 version of tests
  2160  	expBase string
  2161  	expMode string
  2162  }
  2163  
  2164  func (s *shared1620RunnerSuite) TestTLSTime(c *C) {
  2165  	s.freshState(c)
  2166  	runner := repair.NewRunner()
  2167  	err := runner.LoadState()
  2168  	c.Assert(err, IsNil)
  2169  	epoch := time.Unix(0, 0)
  2170  	r := repair.MockTimeNow(func() time.Time {
  2171  		return epoch
  2172  	})
  2173  	defer r()
  2174  	c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true)
  2175  }
  2176  
  2177  func (s *shared1620RunnerSuite) TestLoadStateInitState(c *C) {
  2178  	// sanity
  2179  	c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false)
  2180  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false)
  2181  	// setup realistic seed/assertions
  2182  	r := sysdb.InjectTrusted(s.storeSigning.Trusted)
  2183  	defer r()
  2184  	s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey(""))
  2185  	s.writeSeedAssert(c, "brand.account", s.brandAcct)
  2186  	s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey)
  2187  	s.writeSeedAssert(c, "model", s.modelAs)
  2188  
  2189  	runner := repair.NewRunner()
  2190  	err := runner.LoadState()
  2191  	c.Assert(err, IsNil)
  2192  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true)
  2193  
  2194  	brand, model := runner.BrandModel()
  2195  	c.Check(brand, Equals, "my-brand")
  2196  	c.Check(model, Equals, "my-model-2")
  2197  
  2198  	base, mode := runner.BaseMode()
  2199  	c.Check(base, Equals, s.expBase)
  2200  	c.Check(mode, Equals, s.expMode)
  2201  
  2202  	c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true)
  2203  }
  2204  
  2205  type runner16Suite struct {
  2206  	shared1620RunnerSuite
  2207  }
  2208  
  2209  var _ = Suite(&runner16Suite{})
  2210  
  2211  func (s *runner16Suite) SetUpTest(c *C) {
  2212  	s.shared1620RunnerSuite.SetUpTest(c)
  2213  
  2214  	s.shared1620RunnerSuite.expBase = "core"
  2215  	s.shared1620RunnerSuite.expMode = ""
  2216  
  2217  	s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions")
  2218  
  2219  	// dummy seed yaml
  2220  	err := os.MkdirAll(s.seedAssertsDir, 0755)
  2221  	c.Assert(err, IsNil)
  2222  	seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml")
  2223  	err = ioutil.WriteFile(seedYamlFn, nil, 0644)
  2224  	c.Assert(err, IsNil)
  2225  	seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z")
  2226  	c.Assert(err, IsNil)
  2227  	err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime)
  2228  	c.Assert(err, IsNil)
  2229  	s.seedTime = seedTime
  2230  
  2231  	s.t0 = time.Now().UTC().Truncate(time.Minute)
  2232  
  2233  	s.writeSeedAssert = s.writeSeedAssert16
  2234  }
  2235  
  2236  func (s *runner16Suite) writeSeedAssert16(c *C, fname string, a asserts.Assertion) {
  2237  	err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644)
  2238  	c.Assert(err, IsNil)
  2239  }
  2240  
  2241  func (s *runner16Suite) rmSeedAssert16(c *C, fname string) {
  2242  	err := os.Remove(filepath.Join(s.seedAssertsDir, fname))
  2243  	c.Assert(err, IsNil)
  2244  }
  2245  
  2246  func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) {
  2247  	// sanity
  2248  	c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false)
  2249  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false)
  2250  	// setup realistic seed/assertions
  2251  	r := sysdb.InjectTrusted(s.storeSigning.Trusted)
  2252  	defer r()
  2253  
  2254  	const errPrefix = "cannot set device information: "
  2255  	tests := []struct {
  2256  		breakFunc   func()
  2257  		expectedErr string
  2258  	}{
  2259  		{func() { s.rmSeedAssert16(c, "model") }, errPrefix + "no model assertion in seed data"},
  2260  		{func() { s.rmSeedAssert16(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"},
  2261  		{func() { s.rmSeedAssert16(c, "brand.account-key") }, errPrefix + `cannot find public key.*`},
  2262  		{func() {
  2263  			// broken signature
  2264  			blob := asserts.Encode(s.brandAcct)
  2265  			err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, "brand.account"), blob[:len(blob)-3], 0644)
  2266  			c.Assert(err, IsNil)
  2267  		}, errPrefix + "cannot decode signature:.*"},
  2268  		{func() { s.writeSeedAssert(c, "model2", s.modelAs) }, errPrefix + "multiple models in seed assertions"},
  2269  	}
  2270  
  2271  	for _, test := range tests {
  2272  		s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey(""))
  2273  		s.writeSeedAssert(c, "brand.account", s.brandAcct)
  2274  		s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey)
  2275  		s.writeSeedAssert(c, "model", s.modelAs)
  2276  
  2277  		test.breakFunc()
  2278  
  2279  		runner := repair.NewRunner()
  2280  		err := runner.LoadState()
  2281  		c.Check(err, ErrorMatches, test.expectedErr)
  2282  	}
  2283  }
  2284  
  2285  type runner20Suite struct {
  2286  	shared1620RunnerSuite
  2287  }
  2288  
  2289  var _ = Suite(&runner20Suite{})
  2290  
  2291  var mockModeenv = []byte(`
  2292  mode=run
  2293  model=my-brand/my-model-2
  2294  base=core20_1.snap
  2295  `)
  2296  
  2297  func (s *runner20Suite) SetUpTest(c *C) {
  2298  	s.shared1620RunnerSuite.SetUpTest(c)
  2299  
  2300  	s.shared1620RunnerSuite.expBase = "core20"
  2301  	s.shared1620RunnerSuite.expMode = "run"
  2302  
  2303  	s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions")
  2304  	err := os.MkdirAll(s.seedAssertsDir, 0755)
  2305  	c.Assert(err, IsNil)
  2306  
  2307  	// write dummy modeenv
  2308  	err = os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755)
  2309  	c.Assert(err, IsNil)
  2310  	err = ioutil.WriteFile(dirs.SnapModeenvFile, mockModeenv, 0644)
  2311  	c.Assert(err, IsNil)
  2312  	// validate that modeenv is actually valid
  2313  	_, err = boot.ReadModeenv("")
  2314  	c.Assert(err, IsNil)
  2315  
  2316  	seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z")
  2317  	c.Assert(err, IsNil)
  2318  	err = os.Chtimes(dirs.SnapModeenvFile, seedTime, seedTime)
  2319  	c.Assert(err, IsNil)
  2320  	s.seedTime = seedTime
  2321  	s.t0 = time.Now().UTC().Truncate(time.Minute)
  2322  
  2323  	s.writeSeedAssert = s.writeSeedAssert20
  2324  }
  2325  
  2326  func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertion) {
  2327  	var fn string
  2328  	if _, ok := a.(*asserts.Model); ok {
  2329  		fn = filepath.Join(s.seedAssertsDir, "../model")
  2330  	} else {
  2331  		fn = filepath.Join(s.seedAssertsDir, fname)
  2332  	}
  2333  	err := ioutil.WriteFile(fn, asserts.Encode(a), 0644)
  2334  	c.Assert(err, IsNil)
  2335  
  2336  	// ensure model assertion file has the correct seed time
  2337  	if _, ok := a.(*asserts.Model); ok {
  2338  		err = os.Chtimes(fn, s.seedTime, s.seedTime)
  2339  		c.Assert(err, IsNil)
  2340  	}
  2341  }
  2342  
  2343  func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvInvalidContent(c *C) {
  2344  	runner := repair.NewRunner()
  2345  
  2346  	for _, tc := range []struct {
  2347  		modelStr    string
  2348  		expectedErr string
  2349  	}{
  2350  		{
  2351  			`invalid-key-value`,
  2352  			"cannot set device information: No option model in section ",
  2353  		}, {
  2354  			`model=`,
  2355  			`cannot set device information: cannot find brand/model in modeenv model string ""`,
  2356  		}, {
  2357  			`model=brand-but-no-model`,
  2358  			`cannot set device information: cannot find brand/model in modeenv model string "brand-but-no-model"`,
  2359  		},
  2360  	} {
  2361  		err := ioutil.WriteFile(dirs.SnapModeenvFile, []byte(tc.modelStr), 0644)
  2362  		c.Assert(err, IsNil)
  2363  		err = runner.LoadState()
  2364  		c.Check(err, ErrorMatches, tc.expectedErr)
  2365  	}
  2366  }
  2367  
  2368  func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvIncorrectPermissions(c *C) {
  2369  	runner := repair.NewRunner()
  2370  
  2371  	err := os.Chmod(dirs.SnapModeenvFile, 0300)
  2372  	c.Assert(err, IsNil)
  2373  	s.AddCleanup(func() {
  2374  		err := os.Chmod(dirs.SnapModeenvFile, 0644)
  2375  		c.Assert(err, IsNil)
  2376  	})
  2377  	err = runner.LoadState()
  2378  	c.Check(err, ErrorMatches, "cannot set device information: open /.*/modeenv: permission denied")
  2379  }