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