github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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  const freshStateJSON = `{"device":{"brand":"my-brand","model":"my-model"},"time-lower-bound":"2017-08-11T15:49:49Z"}`
   158  
   159  func (s *baseRunnerSuite) freshState(c *C) {
   160  	err := os.MkdirAll(dirs.SnapRepairDir, 0775)
   161  	c.Assert(err, IsNil)
   162  	err = ioutil.WriteFile(dirs.SnapRepairStateFile, []byte(freshStateJSON), 0600)
   163  	c.Assert(err, IsNil)
   164  }
   165  
   166  type runnerSuite struct {
   167  	baseRunnerSuite
   168  
   169  	restore func()
   170  }
   171  
   172  func (s *runnerSuite) SetUpSuite(c *C) {
   173  	s.baseRunnerSuite.SetUpSuite(c)
   174  	s.restore = snapdenv.SetUserAgentFromVersion("1", nil, "snap-repair")
   175  }
   176  
   177  func (s *runnerSuite) TearDownSuite(c *C) {
   178  	s.restore()
   179  }
   180  
   181  var _ = Suite(&runnerSuite{})
   182  
   183  var (
   184  	testKey = `type: account-key
   185  authority-id: canonical
   186  account-id: canonical
   187  name: repair
   188  public-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   189  since: 2015-11-16T15:04:00Z
   190  body-length: 149
   191  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   192  
   193  AcZrBFaFwYABAvCX5A8dTcdLdhdiuy2YRHO5CAfM5InQefkKOhNMUq2yfi3Sk6trUHxskhZkPnm4
   194  NKx2yRr332q7AJXQHLX+DrZ29ycyoQ2NQGO3eAfQ0hjAAQFYBF8SSh5SutPu5XCVABEBAAE=
   195  
   196  AXNpZw==
   197  `
   198  
   199  	testRepair = `type: repair
   200  authority-id: canonical
   201  brand-id: canonical
   202  repair-id: 2
   203  summary: repair two
   204  architectures:
   205    - amd64
   206    - arm64
   207  series:
   208    - 16
   209  models:
   210    - xyz/frobinator
   211  timestamp: 2017-03-30T12:22:16Z
   212  body-length: 7
   213  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   214  
   215  script
   216  
   217  
   218  AXNpZw==
   219  `
   220  	testHeadersResp = `{"headers":
   221  {"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"}}`
   222  )
   223  
   224  func mustParseURL(s string) *url.URL {
   225  	u, err := url.Parse(s)
   226  	if err != nil {
   227  		panic(err)
   228  	}
   229  	return u
   230  }
   231  
   232  func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) {
   233  	epoch := time.Unix(0, 0)
   234  	r := repair.MockTimeNow(func() time.Time {
   235  		return epoch
   236  	})
   237  	c.Check(runner.TLSTime().Equal(epoch), Equals, true)
   238  	return r
   239  }
   240  
   241  func (s *runnerSuite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) {
   242  	c.Check(runner.TLSTime().Before(s.t0), Equals, false)
   243  }
   244  
   245  func (s *runnerSuite) TestFetchJustRepair(c *C) {
   246  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   247  		ua := r.Header.Get("User-Agent")
   248  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   249  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   250  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   251  		io.WriteString(w, testRepair)
   252  	}))
   253  
   254  	c.Assert(mockServer, NotNil)
   255  	defer mockServer.Close()
   256  
   257  	runner := repair.NewRunner()
   258  	runner.BaseURL = mustParseURL(mockServer.URL)
   259  
   260  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   261  	defer r()
   262  
   263  	repair, aux, err := runner.Fetch("canonical", 2, -1)
   264  	c.Assert(err, IsNil)
   265  	c.Check(repair, NotNil)
   266  	c.Check(aux, HasLen, 0)
   267  	c.Check(repair.BrandID(), Equals, "canonical")
   268  	c.Check(repair.RepairID(), Equals, 2)
   269  	c.Check(repair.Body(), DeepEquals, []byte("script\n"))
   270  
   271  	s.checkBrokenTimeNowMitigated(c, runner)
   272  }
   273  
   274  func (s *runnerSuite) TestFetchScriptTooBig(c *C) {
   275  	restore := repair.MockMaxRepairScriptSize(4)
   276  	defer restore()
   277  
   278  	n := 0
   279  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   280  		n++
   281  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   282  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   283  		io.WriteString(w, testRepair)
   284  	}))
   285  
   286  	c.Assert(mockServer, NotNil)
   287  	defer mockServer.Close()
   288  
   289  	runner := repair.NewRunner()
   290  	runner.BaseURL = mustParseURL(mockServer.URL)
   291  
   292  	_, _, err := runner.Fetch("canonical", 2, -1)
   293  	c.Assert(err, ErrorMatches, `assertion body length 7 exceeds maximum body size 4 for "repair".*`)
   294  	c.Assert(n, Equals, 1)
   295  }
   296  
   297  var (
   298  	testRetryStrategy = retry.LimitCount(5, retry.LimitTime(1*time.Second,
   299  		retry.Exponential{
   300  			Initial: 1 * time.Millisecond,
   301  			Factor:  1,
   302  		},
   303  	))
   304  )
   305  
   306  func (s *runnerSuite) TestFetch500(c *C) {
   307  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   308  	defer restore()
   309  
   310  	n := 0
   311  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   312  		n++
   313  		w.WriteHeader(500)
   314  	}))
   315  
   316  	c.Assert(mockServer, NotNil)
   317  	defer mockServer.Close()
   318  
   319  	runner := repair.NewRunner()
   320  	runner.BaseURL = mustParseURL(mockServer.URL)
   321  
   322  	_, _, err := runner.Fetch("canonical", 2, -1)
   323  	c.Assert(err, ErrorMatches, "cannot fetch repair, unexpected status 500")
   324  	c.Assert(n, Equals, 5)
   325  }
   326  
   327  func (s *runnerSuite) TestFetchEmpty(c *C) {
   328  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   329  	defer restore()
   330  
   331  	n := 0
   332  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   333  		n++
   334  		w.WriteHeader(200)
   335  	}))
   336  
   337  	c.Assert(mockServer, NotNil)
   338  	defer mockServer.Close()
   339  
   340  	runner := repair.NewRunner()
   341  	runner.BaseURL = mustParseURL(mockServer.URL)
   342  
   343  	_, _, err := runner.Fetch("canonical", 2, -1)
   344  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   345  	c.Assert(n, Equals, 5)
   346  }
   347  
   348  func (s *runnerSuite) TestFetchBroken(c *C) {
   349  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   350  	defer restore()
   351  
   352  	n := 0
   353  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   354  		n++
   355  		w.WriteHeader(200)
   356  		io.WriteString(w, "xyz:")
   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) TestFetchNotFound(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(404)
   378  	}))
   379  
   380  	c.Assert(mockServer, NotNil)
   381  	defer mockServer.Close()
   382  
   383  	runner := repair.NewRunner()
   384  	runner.BaseURL = mustParseURL(mockServer.URL)
   385  
   386  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   387  	defer r()
   388  
   389  	_, _, err := runner.Fetch("canonical", 2, -1)
   390  	c.Assert(err, Equals, repair.ErrRepairNotFound)
   391  	c.Assert(n, Equals, 1)
   392  
   393  	s.checkBrokenTimeNowMitigated(c, runner)
   394  }
   395  
   396  func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) {
   397  	restore := repair.MockFetchRetryStrategy(testRetryStrategy)
   398  	defer restore()
   399  
   400  	n := 0
   401  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   402  		n++
   403  		c.Check(r.Header.Get("If-None-Match"), Equals, `"0"`)
   404  		w.WriteHeader(304)
   405  	}))
   406  
   407  	c.Assert(mockServer, NotNil)
   408  	defer mockServer.Close()
   409  
   410  	runner := repair.NewRunner()
   411  	runner.BaseURL = mustParseURL(mockServer.URL)
   412  
   413  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   414  	defer r()
   415  
   416  	_, _, err := runner.Fetch("canonical", 2, 0)
   417  	c.Assert(err, Equals, repair.ErrRepairNotModified)
   418  	c.Assert(n, Equals, 1)
   419  
   420  	s.checkBrokenTimeNowMitigated(c, runner)
   421  }
   422  
   423  func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) {
   424  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   425  		io.WriteString(w, testRepair)
   426  	}))
   427  
   428  	c.Assert(mockServer, NotNil)
   429  	defer mockServer.Close()
   430  
   431  	runner := repair.NewRunner()
   432  	runner.BaseURL = mustParseURL(mockServer.URL)
   433  
   434  	_, _, err := runner.Fetch("canonical", 2, 2)
   435  	c.Assert(err, Equals, repair.ErrRepairNotModified)
   436  }
   437  
   438  func (s *runnerSuite) TestFetchIdMismatch(c *C) {
   439  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   440  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   441  		io.WriteString(w, testRepair)
   442  	}))
   443  
   444  	c.Assert(mockServer, NotNil)
   445  	defer mockServer.Close()
   446  
   447  	runner := repair.NewRunner()
   448  	runner.BaseURL = mustParseURL(mockServer.URL)
   449  
   450  	_, _, err := runner.Fetch("canonical", 4, -1)
   451  	c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`)
   452  }
   453  
   454  func (s *runnerSuite) TestFetchWrongFirstType(c *C) {
   455  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   456  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   457  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   458  		io.WriteString(w, testKey)
   459  	}))
   460  
   461  	c.Assert(mockServer, NotNil)
   462  	defer mockServer.Close()
   463  
   464  	runner := repair.NewRunner()
   465  	runner.BaseURL = mustParseURL(mockServer.URL)
   466  
   467  	_, _, err := runner.Fetch("canonical", 2, -1)
   468  	c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`)
   469  }
   470  
   471  func (s *runnerSuite) TestFetchRepairPlusKey(c *C) {
   472  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   473  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   474  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   475  		io.WriteString(w, testRepair)
   476  		io.WriteString(w, "\n")
   477  		io.WriteString(w, testKey)
   478  	}))
   479  
   480  	c.Assert(mockServer, NotNil)
   481  	defer mockServer.Close()
   482  
   483  	runner := repair.NewRunner()
   484  	runner.BaseURL = mustParseURL(mockServer.URL)
   485  
   486  	repair, aux, err := runner.Fetch("canonical", 2, -1)
   487  	c.Assert(err, IsNil)
   488  	c.Check(repair, NotNil)
   489  	c.Check(aux, HasLen, 1)
   490  	_, ok := aux[0].(*asserts.AccountKey)
   491  	c.Check(ok, Equals, true)
   492  }
   493  
   494  func (s *runnerSuite) TestPeek(c *C) {
   495  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   496  		ua := r.Header.Get("User-Agent")
   497  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   498  		c.Check(r.Header.Get("Accept"), Equals, "application/json")
   499  		c.Check(r.URL.Path, Equals, "/repairs/canonical/2")
   500  		io.WriteString(w, testHeadersResp)
   501  	}))
   502  
   503  	c.Assert(mockServer, NotNil)
   504  	defer mockServer.Close()
   505  
   506  	runner := repair.NewRunner()
   507  	runner.BaseURL = mustParseURL(mockServer.URL)
   508  
   509  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   510  	defer r()
   511  
   512  	h, err := runner.Peek("canonical", 2)
   513  	c.Assert(err, IsNil)
   514  	c.Check(h["series"], DeepEquals, []interface{}{"16"})
   515  	c.Check(h["architectures"], DeepEquals, []interface{}{"amd64", "arm64"})
   516  	c.Check(h["models"], DeepEquals, []interface{}{"xyz/frobinator"})
   517  
   518  	s.checkBrokenTimeNowMitigated(c, runner)
   519  }
   520  
   521  func (s *runnerSuite) TestPeek500(c *C) {
   522  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
   523  	defer restore()
   524  
   525  	n := 0
   526  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   527  		n++
   528  		w.WriteHeader(500)
   529  	}))
   530  
   531  	c.Assert(mockServer, NotNil)
   532  	defer mockServer.Close()
   533  
   534  	runner := repair.NewRunner()
   535  	runner.BaseURL = mustParseURL(mockServer.URL)
   536  
   537  	_, err := runner.Peek("canonical", 2)
   538  	c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500")
   539  	c.Assert(n, Equals, 5)
   540  }
   541  
   542  func (s *runnerSuite) TestPeekInvalid(c *C) {
   543  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
   544  	defer restore()
   545  
   546  	n := 0
   547  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   548  		n++
   549  		w.WriteHeader(200)
   550  		io.WriteString(w, "{")
   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, Equals, io.ErrUnexpectedEOF)
   561  	c.Assert(n, Equals, 5)
   562  }
   563  
   564  func (s *runnerSuite) TestPeekNotFound(c *C) {
   565  	n := 0
   566  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   567  		n++
   568  		w.WriteHeader(404)
   569  	}))
   570  
   571  	c.Assert(mockServer, NotNil)
   572  	defer mockServer.Close()
   573  
   574  	runner := repair.NewRunner()
   575  	runner.BaseURL = mustParseURL(mockServer.URL)
   576  
   577  	r := s.mockBrokenTimeNowSetToEpoch(c, runner)
   578  	defer r()
   579  
   580  	_, err := runner.Peek("canonical", 2)
   581  	c.Assert(err, Equals, repair.ErrRepairNotFound)
   582  	c.Assert(n, Equals, 1)
   583  
   584  	s.checkBrokenTimeNowMitigated(c, runner)
   585  }
   586  
   587  func (s *runnerSuite) TestPeekIdMismatch(c *C) {
   588  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   589  		c.Check(r.Header.Get("Accept"), Equals, "application/json")
   590  		io.WriteString(w, testHeadersResp)
   591  	}))
   592  
   593  	c.Assert(mockServer, NotNil)
   594  	defer mockServer.Close()
   595  
   596  	runner := repair.NewRunner()
   597  	runner.BaseURL = mustParseURL(mockServer.URL)
   598  
   599  	_, err := runner.Peek("canonical", 4)
   600  	c.Assert(err, ErrorMatches, `cannot peek repair headers, repair id mismatch canonical/2 != canonical/4`)
   601  }
   602  
   603  func (s *runnerSuite) TestLoadState(c *C) {
   604  	s.freshState(c)
   605  
   606  	runner := repair.NewRunner()
   607  	err := runner.LoadState()
   608  	c.Assert(err, IsNil)
   609  	brand, model := runner.BrandModel()
   610  	c.Check(brand, Equals, "my-brand")
   611  	c.Check(model, Equals, "my-model")
   612  }
   613  
   614  func (s *runnerSuite) TestLoadStateInitStateFail(c *C) {
   615  	err := os.MkdirAll(dirs.SnapSeedDir, 0755)
   616  	c.Assert(err, IsNil)
   617  
   618  	restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir))
   619  	defer restore()
   620  
   621  	runner := repair.NewRunner()
   622  	err = runner.LoadState()
   623  	c.Check(err, ErrorMatches, `cannot create repair state directory:.*`)
   624  }
   625  
   626  func (s *runnerSuite) TestSaveStateFail(c *C) {
   627  	s.freshState(c)
   628  
   629  	runner := repair.NewRunner()
   630  	err := runner.LoadState()
   631  	c.Assert(err, IsNil)
   632  
   633  	restore := makeReadOnly(c, dirs.SnapRepairDir)
   634  	defer restore()
   635  
   636  	// no error because this is a no-op
   637  	err = runner.SaveState()
   638  	c.Check(err, IsNil)
   639  
   640  	// mark as modified
   641  	runner.SetStateModified(true)
   642  
   643  	err = runner.SaveState()
   644  	c.Check(err, ErrorMatches, `cannot save repair state:.*`)
   645  }
   646  
   647  func (s *runnerSuite) TestSaveState(c *C) {
   648  	s.freshState(c)
   649  
   650  	runner := repair.NewRunner()
   651  	err := runner.LoadState()
   652  	c.Assert(err, IsNil)
   653  
   654  	runner.SetSequence("canonical", []*repair.RepairState{
   655  		{Sequence: 1, Revision: 3},
   656  	})
   657  	// mark as modified
   658  	runner.SetStateModified(true)
   659  
   660  	err = runner.SaveState()
   661  	c.Assert(err, IsNil)
   662  
   663  	c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, `{"device":{"brand":"my-brand","model":"my-model"},"sequences":{"canonical":[{"sequence":1,"revision":3,"status":0}]},"time-lower-bound":"2017-08-11T15:49:49Z"}`)
   664  }
   665  
   666  func (s *runnerSuite) TestApplicable(c *C) {
   667  	s.freshState(c)
   668  	runner := repair.NewRunner()
   669  	err := runner.LoadState()
   670  	c.Assert(err, IsNil)
   671  
   672  	scenarios := []struct {
   673  		headers    map[string]interface{}
   674  		applicable bool
   675  	}{
   676  		{nil, true},
   677  		{map[string]interface{}{"series": []interface{}{"18"}}, false},
   678  		{map[string]interface{}{"series": []interface{}{"18", "16"}}, true},
   679  		{map[string]interface{}{"series": "18"}, false},
   680  		{map[string]interface{}{"series": []interface{}{18}}, false},
   681  		{map[string]interface{}{"architectures": []interface{}{arch.DpkgArchitecture()}}, true},
   682  		{map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false},
   683  		{map[string]interface{}{"architectures": []interface{}{"other-arch", arch.DpkgArchitecture()}}, true},
   684  		{map[string]interface{}{"architectures": arch.DpkgArchitecture()}, false},
   685  		{map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true},
   686  		{map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false},
   687  		{map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true},
   688  		{map[string]interface{}{"models": "my-brand/my-model"}, false},
   689  		// model prefix matches
   690  		{map[string]interface{}{"models": []interface{}{"my-brand/*"}}, true},
   691  		{map[string]interface{}{"models": []interface{}{"my-brand/my-mod*"}}, true},
   692  		{map[string]interface{}{"models": []interface{}{"my-brand/xxx*"}}, false},
   693  		{map[string]interface{}{"models": []interface{}{"my-brand/my-mod*", "my-brand/xxx*"}}, true},
   694  		{map[string]interface{}{"models": []interface{}{"my*"}}, false},
   695  		{map[string]interface{}{"disabled": "true"}, false},
   696  		{map[string]interface{}{"disabled": "false"}, true},
   697  	}
   698  
   699  	for _, scen := range scenarios {
   700  		ok := runner.Applicable(scen.headers)
   701  		c.Check(ok, Equals, scen.applicable, Commentf("%v", scen))
   702  	}
   703  }
   704  
   705  var (
   706  	nextRepairs = []string{`type: repair
   707  authority-id: canonical
   708  brand-id: canonical
   709  repair-id: 1
   710  summary: repair one
   711  timestamp: 2017-07-01T12:00:00Z
   712  body-length: 8
   713  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   714  
   715  scriptA
   716  
   717  
   718  AXNpZw==`,
   719  		`type: repair
   720  authority-id: canonical
   721  brand-id: canonical
   722  repair-id: 2
   723  summary: repair two
   724  series:
   725    - 33
   726  timestamp: 2017-07-02T12:00:00Z
   727  body-length: 8
   728  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   729  
   730  scriptB
   731  
   732  
   733  AXNpZw==`,
   734  		`type: repair
   735  revision: 2
   736  authority-id: canonical
   737  brand-id: canonical
   738  repair-id: 3
   739  summary: repair three rev2
   740  series:
   741    - 16
   742  timestamp: 2017-07-03T12:00:00Z
   743  body-length: 8
   744  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   745  
   746  scriptC
   747  
   748  
   749  AXNpZw==
   750  `}
   751  
   752  	repair3Rev4 = `type: repair
   753  revision: 4
   754  authority-id: canonical
   755  brand-id: canonical
   756  repair-id: 3
   757  summary: repair three rev4
   758  series:
   759    - 16
   760  timestamp: 2017-07-03T12:00:00Z
   761  body-length: 9
   762  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   763  
   764  scriptC2
   765  
   766  
   767  AXNpZw==
   768  `
   769  
   770  	repair4 = `type: repair
   771  authority-id: canonical
   772  brand-id: canonical
   773  repair-id: 4
   774  summary: repair four
   775  timestamp: 2017-07-03T12:00:00Z
   776  body-length: 8
   777  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   778  
   779  scriptD
   780  
   781  
   782  AXNpZw==
   783  `
   784  )
   785  
   786  func makeMockServer(c *C, seqRepairs *[]string, redirectFirst bool) *httptest.Server {
   787  	var mockServer *httptest.Server
   788  	mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   789  		ua := r.Header.Get("User-Agent")
   790  		c.Check(strings.Contains(ua, "snap-repair"), Equals, true)
   791  
   792  		urlPath := r.URL.Path
   793  		if redirectFirst && r.Header.Get("Accept") == asserts.MediaType {
   794  			if !strings.HasPrefix(urlPath, "/final/") {
   795  				// redirect
   796  				finalURL := mockServer.URL + "/final" + r.URL.Path
   797  				w.Header().Set("Location", finalURL)
   798  				w.WriteHeader(302)
   799  				return
   800  			}
   801  			urlPath = strings.TrimPrefix(urlPath, "/final")
   802  		}
   803  
   804  		c.Check(strings.HasPrefix(urlPath, "/repairs/canonical/"), Equals, true)
   805  
   806  		seq, err := strconv.Atoi(strings.TrimPrefix(urlPath, "/repairs/canonical/"))
   807  		c.Assert(err, IsNil)
   808  
   809  		if seq > len(*seqRepairs) {
   810  			w.WriteHeader(404)
   811  			return
   812  		}
   813  
   814  		rpr := []byte((*seqRepairs)[seq-1])
   815  		dec := asserts.NewDecoder(bytes.NewBuffer(rpr))
   816  		repair, err := dec.Decode()
   817  		c.Assert(err, IsNil)
   818  
   819  		switch r.Header.Get("Accept") {
   820  		case "application/json":
   821  			b, err := json.Marshal(map[string]interface{}{
   822  				"headers": repair.Headers(),
   823  			})
   824  			c.Assert(err, IsNil)
   825  			w.Write(b)
   826  		case asserts.MediaType:
   827  			etag := fmt.Sprintf(`"%d"`, repair.Revision())
   828  			if strings.Contains(r.Header.Get("If-None-Match"), etag) {
   829  				w.WriteHeader(304)
   830  				return
   831  			}
   832  			w.Write(rpr)
   833  		}
   834  	}))
   835  
   836  	c.Assert(mockServer, NotNil)
   837  
   838  	return mockServer
   839  }
   840  
   841  func (s *runnerSuite) TestTrustedRepairRootKeys(c *C) {
   842  	acctKeys := repair.TrustedRepairRootKeys()
   843  	c.Check(acctKeys, HasLen, 1)
   844  	c.Check(acctKeys[0].AccountID(), Equals, "canonical")
   845  	c.Check(acctKeys[0].PublicKeyID(), Equals, "nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t")
   846  }
   847  
   848  func (s *runnerSuite) TestVerify(c *C) {
   849  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
   850  	defer r1()
   851  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
   852  	defer r2()
   853  
   854  	runner := repair.NewRunner()
   855  
   856  	a, err := s.repairsSigning.Sign(asserts.RepairType, map[string]interface{}{
   857  		"brand-id":  "canonical",
   858  		"repair-id": "2",
   859  		"summary":   "repair two",
   860  		"timestamp": time.Now().UTC().Format(time.RFC3339),
   861  	}, []byte("#script"), "")
   862  	c.Assert(err, IsNil)
   863  	rpr := a.(*asserts.Repair)
   864  
   865  	err = runner.Verify(rpr, []asserts.Assertion{s.repairsAcctKey})
   866  	c.Check(err, IsNil)
   867  }
   868  
   869  func (s *runnerSuite) signSeqRepairs(c *C, repairs []string) []string {
   870  	var seq []string
   871  	for _, rpr := range repairs {
   872  		decoded, err := asserts.Decode([]byte(rpr))
   873  		c.Assert(err, IsNil)
   874  		signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "")
   875  		c.Assert(err, IsNil)
   876  		buf := &bytes.Buffer{}
   877  		enc := asserts.NewEncoder(buf)
   878  		enc.Encode(signed)
   879  		enc.Encode(s.repairsAcctKey)
   880  		seq = append(seq, buf.String())
   881  	}
   882  	return seq
   883  }
   884  
   885  func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState {
   886  	data, err := ioutil.ReadFile(dirs.SnapRepairStateFile)
   887  	c.Assert(err, IsNil)
   888  	var x struct {
   889  		Sequences map[string][]*repair.RepairState `json:"sequences"`
   890  	}
   891  	err = json.Unmarshal(data, &x)
   892  	c.Assert(err, IsNil)
   893  	return x.Sequences
   894  }
   895  
   896  func (s *runnerSuite) testNext(c *C, redirectFirst bool) {
   897  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
   898  	defer r1()
   899  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
   900  	defer r2()
   901  
   902  	seqRepairs := s.signSeqRepairs(c, nextRepairs)
   903  
   904  	mockServer := makeMockServer(c, &seqRepairs, redirectFirst)
   905  	defer mockServer.Close()
   906  
   907  	runner := repair.NewRunner()
   908  	runner.BaseURL = mustParseURL(mockServer.URL)
   909  	runner.LoadState()
   910  
   911  	rpr, err := runner.Next("canonical")
   912  	c.Assert(err, IsNil)
   913  	c.Check(rpr.RepairID(), Equals, 1)
   914  	c.Check(osutil.FileExists(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "1", "r0.repair")), Equals, true)
   915  
   916  	rpr, err = runner.Next("canonical")
   917  	c.Assert(err, IsNil)
   918  	c.Check(rpr.RepairID(), Equals, 3)
   919  	c.Check(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "3", "r2.repair"), testutil.FileEquals, seqRepairs[2])
   920  
   921  	// no more
   922  	rpr, err = runner.Next("canonical")
   923  	c.Check(err, Equals, repair.ErrRepairNotFound)
   924  
   925  	expectedSeq := []*repair.RepairState{
   926  		{Sequence: 1},
   927  		{Sequence: 2, Status: repair.SkipStatus},
   928  		{Sequence: 3, Revision: 2},
   929  	}
   930  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
   931  	// on disk
   932  	seqs := s.loadSequences(c)
   933  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
   934  
   935  	// start fresh run with new runner
   936  	// will refetch repair 3
   937  	signed := s.signSeqRepairs(c, []string{repair3Rev4, repair4})
   938  	seqRepairs[2] = signed[0]
   939  	seqRepairs = append(seqRepairs, signed[1])
   940  
   941  	runner = repair.NewRunner()
   942  	runner.BaseURL = mustParseURL(mockServer.URL)
   943  	runner.LoadState()
   944  
   945  	rpr, err = runner.Next("canonical")
   946  	c.Assert(err, IsNil)
   947  	c.Check(rpr.RepairID(), Equals, 1)
   948  
   949  	rpr, err = runner.Next("canonical")
   950  	c.Assert(err, IsNil)
   951  	c.Check(rpr.RepairID(), Equals, 3)
   952  	// refetched new revision!
   953  	c.Check(rpr.Revision(), Equals, 4)
   954  	c.Check(rpr.Body(), DeepEquals, []byte("scriptC2\n"))
   955  
   956  	// new repair
   957  	rpr, err = runner.Next("canonical")
   958  	c.Assert(err, IsNil)
   959  	c.Check(rpr.RepairID(), Equals, 4)
   960  	c.Check(rpr.Body(), DeepEquals, []byte("scriptD\n"))
   961  
   962  	// no more
   963  	rpr, err = runner.Next("canonical")
   964  	c.Check(err, Equals, repair.ErrRepairNotFound)
   965  
   966  	c.Check(runner.Sequence("canonical"), DeepEquals, []*repair.RepairState{
   967  		{Sequence: 1},
   968  		{Sequence: 2, Status: repair.SkipStatus},
   969  		{Sequence: 3, Revision: 4},
   970  		{Sequence: 4},
   971  	})
   972  }
   973  
   974  func (s *runnerSuite) TestNext(c *C) {
   975  	redirectFirst := false
   976  	s.testNext(c, redirectFirst)
   977  }
   978  
   979  func (s *runnerSuite) TestNextRedirect(c *C) {
   980  	redirectFirst := true
   981  	s.testNext(c, redirectFirst)
   982  }
   983  
   984  func (s *runnerSuite) TestNextImmediateSkip(c *C) {
   985  	seqRepairs := []string{`type: repair
   986  authority-id: canonical
   987  brand-id: canonical
   988  repair-id: 1
   989  summary: repair one
   990  series:
   991    - 33
   992  timestamp: 2017-07-02T12:00:00Z
   993  body-length: 8
   994  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
   995  
   996  scriptB
   997  
   998  
   999  AXNpZw==`}
  1000  
  1001  	mockServer := makeMockServer(c, &seqRepairs, false)
  1002  	defer mockServer.Close()
  1003  
  1004  	runner := repair.NewRunner()
  1005  	runner.BaseURL = mustParseURL(mockServer.URL)
  1006  	runner.LoadState()
  1007  
  1008  	// not applicable => not returned
  1009  	_, err := runner.Next("canonical")
  1010  	c.Check(err, Equals, repair.ErrRepairNotFound)
  1011  
  1012  	expectedSeq := []*repair.RepairState{
  1013  		{Sequence: 1, Status: repair.SkipStatus},
  1014  	}
  1015  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1016  	// on disk
  1017  	seqs := s.loadSequences(c)
  1018  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1019  }
  1020  
  1021  func (s *runnerSuite) TestNextRefetchSkip(c *C) {
  1022  	seqRepairs := []string{`type: repair
  1023  authority-id: canonical
  1024  brand-id: canonical
  1025  repair-id: 1
  1026  summary: repair one
  1027  series:
  1028    - 16
  1029  timestamp: 2017-07-02T12:00:00Z
  1030  body-length: 8
  1031  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1032  
  1033  scriptB
  1034  
  1035  
  1036  AXNpZw==`}
  1037  
  1038  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1039  	defer r1()
  1040  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1041  	defer r2()
  1042  
  1043  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1044  
  1045  	mockServer := makeMockServer(c, &seqRepairs, false)
  1046  	defer mockServer.Close()
  1047  
  1048  	runner := repair.NewRunner()
  1049  	runner.BaseURL = mustParseURL(mockServer.URL)
  1050  	runner.LoadState()
  1051  
  1052  	_, err := runner.Next("canonical")
  1053  	c.Assert(err, IsNil)
  1054  
  1055  	expectedSeq := []*repair.RepairState{
  1056  		{Sequence: 1},
  1057  	}
  1058  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1059  	// on disk
  1060  	seqs := s.loadSequences(c)
  1061  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1062  
  1063  	// new fresh run, repair becomes now unapplicable
  1064  	seqRepairs[0] = `type: repair
  1065  authority-id: canonical
  1066  revision: 1
  1067  brand-id: canonical
  1068  repair-id: 1
  1069  summary: repair one rev1
  1070  series:
  1071    - 16
  1072  disabled: true
  1073  timestamp: 2017-07-02T12:00:00Z
  1074  body-length: 7
  1075  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1076  
  1077  scriptX
  1078  
  1079  AXNpZw==`
  1080  
  1081  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1082  
  1083  	runner = repair.NewRunner()
  1084  	runner.BaseURL = mustParseURL(mockServer.URL)
  1085  	runner.LoadState()
  1086  
  1087  	_, err = runner.Next("canonical")
  1088  	c.Check(err, Equals, repair.ErrRepairNotFound)
  1089  
  1090  	expectedSeq = []*repair.RepairState{
  1091  		{Sequence: 1, Revision: 1, Status: repair.SkipStatus},
  1092  	}
  1093  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1094  	// on disk
  1095  	seqs = s.loadSequences(c)
  1096  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1097  }
  1098  
  1099  func (s *runnerSuite) TestNext500(c *C) {
  1100  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
  1101  	defer restore()
  1102  
  1103  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1104  		w.WriteHeader(500)
  1105  	}))
  1106  
  1107  	c.Assert(mockServer, NotNil)
  1108  	defer mockServer.Close()
  1109  
  1110  	runner := repair.NewRunner()
  1111  	runner.BaseURL = mustParseURL(mockServer.URL)
  1112  	runner.LoadState()
  1113  
  1114  	_, err := runner.Next("canonical")
  1115  	c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500")
  1116  }
  1117  
  1118  func (s *runnerSuite) TestNextNotFound(c *C) {
  1119  	s.freshState(c)
  1120  
  1121  	restore := repair.MockPeekRetryStrategy(testRetryStrategy)
  1122  	defer restore()
  1123  
  1124  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1125  		w.WriteHeader(404)
  1126  	}))
  1127  
  1128  	c.Assert(mockServer, NotNil)
  1129  	defer mockServer.Close()
  1130  
  1131  	runner := repair.NewRunner()
  1132  	runner.BaseURL = mustParseURL(mockServer.URL)
  1133  	runner.LoadState()
  1134  
  1135  	// sanity
  1136  	c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, freshStateJSON)
  1137  
  1138  	_, err := runner.Next("canonical")
  1139  	c.Assert(err, Equals, repair.ErrRepairNotFound)
  1140  
  1141  	// we saved new time lower bound
  1142  	t1 := runner.TimeLowerBound()
  1143  	expected := strings.Replace(freshStateJSON, "2017-08-11T15:49:49Z", t1.Format(time.RFC3339), 1)
  1144  	c.Check(expected, Not(Equals), freshStateJSON)
  1145  	c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, expected)
  1146  }
  1147  
  1148  func (s *runnerSuite) TestNextSaveStateError(c *C) {
  1149  	seqRepairs := []string{`type: repair
  1150  authority-id: canonical
  1151  brand-id: canonical
  1152  repair-id: 1
  1153  summary: repair one
  1154  series:
  1155    - 33
  1156  timestamp: 2017-07-02T12:00:00Z
  1157  body-length: 8
  1158  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1159  
  1160  scriptB
  1161  
  1162  
  1163  AXNpZw==`}
  1164  
  1165  	mockServer := makeMockServer(c, &seqRepairs, false)
  1166  	defer mockServer.Close()
  1167  
  1168  	runner := repair.NewRunner()
  1169  	runner.BaseURL = mustParseURL(mockServer.URL)
  1170  	runner.LoadState()
  1171  
  1172  	// break SaveState
  1173  	restore := makeReadOnly(c, dirs.SnapRepairDir)
  1174  	defer restore()
  1175  
  1176  	_, err := runner.Next("canonical")
  1177  	c.Check(err, ErrorMatches, `cannot save repair state:.*`)
  1178  }
  1179  
  1180  func (s *runnerSuite) TestNextVerifyNoKey(c *C) {
  1181  	seqRepairs := []string{`type: repair
  1182  authority-id: canonical
  1183  brand-id: canonical
  1184  repair-id: 1
  1185  summary: repair one
  1186  timestamp: 2017-07-02T12:00:00Z
  1187  body-length: 8
  1188  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1189  
  1190  scriptB
  1191  
  1192  
  1193  AXNpZw==`}
  1194  
  1195  	mockServer := makeMockServer(c, &seqRepairs, false)
  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.Check(err, ErrorMatches, `cannot verify repair canonical-1: cannot find public key.*`)
  1204  
  1205  	c.Check(runner.Sequence("canonical"), HasLen, 0)
  1206  }
  1207  
  1208  func (s *runnerSuite) TestNextVerifySelfSigned(c *C) {
  1209  	randoKey, _ := assertstest.GenerateKey(752)
  1210  
  1211  	randomSigning := assertstest.NewSigningDB("canonical", randoKey)
  1212  	randoKeyEncoded, err := asserts.EncodePublicKey(randoKey.PublicKey())
  1213  	c.Assert(err, IsNil)
  1214  	acctKey, err := randomSigning.Sign(asserts.AccountKeyType, map[string]interface{}{
  1215  		"authority-id":        "canonical",
  1216  		"account-id":          "canonical",
  1217  		"public-key-sha3-384": randoKey.PublicKey().ID(),
  1218  		"name":                "repairs",
  1219  		"since":               time.Now().UTC().Format(time.RFC3339),
  1220  	}, randoKeyEncoded, "")
  1221  	c.Assert(err, IsNil)
  1222  
  1223  	rpr, err := randomSigning.Sign(asserts.RepairType, map[string]interface{}{
  1224  		"brand-id":  "canonical",
  1225  		"repair-id": "1",
  1226  		"summary":   "repair one",
  1227  		"timestamp": time.Now().UTC().Format(time.RFC3339),
  1228  	}, []byte("scriptB\n"), "")
  1229  	c.Assert(err, IsNil)
  1230  
  1231  	buf := &bytes.Buffer{}
  1232  	enc := asserts.NewEncoder(buf)
  1233  	enc.Encode(rpr)
  1234  	enc.Encode(acctKey)
  1235  	seqRepairs := []string{buf.String()}
  1236  
  1237  	mockServer := makeMockServer(c, &seqRepairs, false)
  1238  	defer mockServer.Close()
  1239  
  1240  	runner := repair.NewRunner()
  1241  	runner.BaseURL = mustParseURL(mockServer.URL)
  1242  	runner.LoadState()
  1243  
  1244  	_, err = runner.Next("canonical")
  1245  	c.Check(err, ErrorMatches, `cannot verify repair canonical-1: circular assertions`)
  1246  
  1247  	c.Check(runner.Sequence("canonical"), HasLen, 0)
  1248  }
  1249  
  1250  func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) {
  1251  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1252  	defer r1()
  1253  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1254  	defer r2()
  1255  
  1256  	decoded, err := asserts.Decode([]byte(nextRepairs[0]))
  1257  	c.Assert(err, IsNil)
  1258  	signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "")
  1259  	c.Assert(err, IsNil)
  1260  
  1261  	// stream with all keys (any order) works as well
  1262  	buf := &bytes.Buffer{}
  1263  	enc := asserts.NewEncoder(buf)
  1264  	enc.Encode(signed)
  1265  	enc.Encode(s.storeSigning.TrustedKey)
  1266  	enc.Encode(s.repairRootAcctKey)
  1267  	enc.Encode(s.repairsAcctKey)
  1268  	seqRepairs := []string{buf.String()}
  1269  
  1270  	mockServer := makeMockServer(c, &seqRepairs, false)
  1271  	defer mockServer.Close()
  1272  
  1273  	runner := repair.NewRunner()
  1274  	runner.BaseURL = mustParseURL(mockServer.URL)
  1275  	runner.LoadState()
  1276  
  1277  	rpr, err := runner.Next("canonical")
  1278  	c.Assert(err, IsNil)
  1279  	c.Check(rpr.RepairID(), Equals, 1)
  1280  }
  1281  
  1282  func (s *runnerSuite) TestRepairSetStatus(c *C) {
  1283  	seqRepairs := []string{`type: repair
  1284  authority-id: canonical
  1285  brand-id: canonical
  1286  repair-id: 1
  1287  summary: repair one
  1288  timestamp: 2017-07-02T12:00:00Z
  1289  body-length: 8
  1290  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1291  
  1292  scriptB
  1293  
  1294  
  1295  AXNpZw==`}
  1296  
  1297  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1298  	defer r1()
  1299  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1300  	defer r2()
  1301  
  1302  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1303  
  1304  	mockServer := makeMockServer(c, &seqRepairs, false)
  1305  	defer mockServer.Close()
  1306  
  1307  	runner := repair.NewRunner()
  1308  	runner.BaseURL = mustParseURL(mockServer.URL)
  1309  	runner.LoadState()
  1310  
  1311  	rpr, err := runner.Next("canonical")
  1312  	c.Assert(err, IsNil)
  1313  
  1314  	rpr.SetStatus(repair.DoneStatus)
  1315  
  1316  	expectedSeq := []*repair.RepairState{
  1317  		{Sequence: 1, Status: repair.DoneStatus},
  1318  	}
  1319  	c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq)
  1320  	// on disk
  1321  	seqs := s.loadSequences(c)
  1322  	c.Check(seqs["canonical"], DeepEquals, expectedSeq)
  1323  }
  1324  
  1325  func (s *runnerSuite) TestRepairBasicRun(c *C) {
  1326  	seqRepairs := []string{`type: repair
  1327  authority-id: canonical
  1328  brand-id: canonical
  1329  repair-id: 1
  1330  summary: repair one
  1331  series:
  1332    - 16
  1333  timestamp: 2017-07-02T12:00:00Z
  1334  body-length: 17
  1335  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1336  
  1337  #!/bin/sh
  1338  exit 0
  1339  
  1340  
  1341  AXNpZw==`}
  1342  
  1343  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1344  	defer r1()
  1345  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1346  	defer r2()
  1347  
  1348  	seqRepairs = s.signSeqRepairs(c, seqRepairs)
  1349  
  1350  	mockServer := makeMockServer(c, &seqRepairs, false)
  1351  	defer mockServer.Close()
  1352  
  1353  	runner := repair.NewRunner()
  1354  	runner.BaseURL = mustParseURL(mockServer.URL)
  1355  	runner.LoadState()
  1356  
  1357  	rpr, err := runner.Next("canonical")
  1358  	c.Assert(err, IsNil)
  1359  
  1360  	err = rpr.Run()
  1361  	c.Assert(err, IsNil)
  1362  	c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, "#!/bin/sh\nexit 0\n")
  1363  }
  1364  
  1365  func makeMockRepair(script string) string {
  1366  	return fmt.Sprintf(`type: repair
  1367  authority-id: canonical
  1368  brand-id: canonical
  1369  repair-id: 1
  1370  summary: repair one
  1371  series:
  1372    - 16
  1373  timestamp: 2017-07-02T12:00:00Z
  1374  body-length: %d
  1375  sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
  1376  
  1377  %s
  1378  
  1379  AXNpZw==`, len(script), script)
  1380  }
  1381  
  1382  func verifyRepairStatus(c *C, status repair.RepairStatus) {
  1383  	c.Check(dirs.SnapRepairStateFile, testutil.FileContains, fmt.Sprintf(`{"device":{"brand":"","model":""},"sequences":{"canonical":[{"sequence":1,"revision":0,"status":%d}`, status))
  1384  }
  1385  
  1386  // tests related to correct execution of script
  1387  type runScriptSuite struct {
  1388  	baseRunnerSuite
  1389  
  1390  	seqRepairs []string
  1391  
  1392  	mockServer *httptest.Server
  1393  	runner     *repair.Runner
  1394  
  1395  	runDir string
  1396  
  1397  	restoreErrTrackerReportRepair func()
  1398  	errReport                     struct {
  1399  		repair string
  1400  		errMsg string
  1401  		dupSig string
  1402  		extra  map[string]string
  1403  	}
  1404  }
  1405  
  1406  var _ = Suite(&runScriptSuite{})
  1407  
  1408  func (s *runScriptSuite) SetUpTest(c *C) {
  1409  	s.baseRunnerSuite.SetUpTest(c)
  1410  
  1411  	s.mockServer = makeMockServer(c, &s.seqRepairs, false)
  1412  	s.AddCleanup(func() { s.mockServer.Close() })
  1413  
  1414  	s.runner = repair.NewRunner()
  1415  	s.runner.BaseURL = mustParseURL(s.mockServer.URL)
  1416  	s.runner.LoadState()
  1417  
  1418  	s.runDir = filepath.Join(dirs.SnapRepairRunDir, "canonical", "1")
  1419  
  1420  	restoreErrTrackerReportRepair := repair.MockErrtrackerReportRepair(s.errtrackerReportRepair)
  1421  	s.AddCleanup(restoreErrTrackerReportRepair)
  1422  }
  1423  
  1424  func (s *runScriptSuite) errtrackerReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) {
  1425  	s.errReport.repair = repair
  1426  	s.errReport.errMsg = errMsg
  1427  	s.errReport.dupSig = dupSig
  1428  	s.errReport.extra = extra
  1429  
  1430  	return "some-oops-id", nil
  1431  }
  1432  
  1433  func (s *runScriptSuite) testScriptRun(c *C, mockScript string) *repair.Repair {
  1434  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1435  	defer r1()
  1436  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1437  	defer r2()
  1438  
  1439  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  1440  
  1441  	rpr, err := s.runner.Next("canonical")
  1442  	c.Assert(err, IsNil)
  1443  
  1444  	err = rpr.Run()
  1445  	c.Assert(err, IsNil)
  1446  
  1447  	c.Check(filepath.Join(s.runDir, "r0.script"), testutil.FileEquals, mockScript)
  1448  
  1449  	return rpr
  1450  }
  1451  
  1452  func (s *runScriptSuite) verifyRundir(c *C, names []string) {
  1453  	dirents, err := ioutil.ReadDir(s.runDir)
  1454  	c.Assert(err, IsNil)
  1455  	c.Assert(dirents, HasLen, len(names))
  1456  	for i := range dirents {
  1457  		c.Check(dirents[i].Name(), Matches, names[i])
  1458  	}
  1459  }
  1460  
  1461  type byMtime []os.FileInfo
  1462  
  1463  func (m byMtime) Len() int           { return len(m) }
  1464  func (m byMtime) Less(i, j int) bool { return m[i].ModTime().Before(m[j].ModTime()) }
  1465  func (m byMtime) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
  1466  
  1467  func (s *runScriptSuite) verifyOutput(c *C, name, expectedOutput string) {
  1468  	c.Check(filepath.Join(s.runDir, name), testutil.FileEquals, expectedOutput)
  1469  	// ensure correct permissions
  1470  	fi, err := os.Stat(filepath.Join(s.runDir, name))
  1471  	c.Assert(err, IsNil)
  1472  	c.Check(fi.Mode(), Equals, os.FileMode(0600))
  1473  }
  1474  
  1475  func (s *runScriptSuite) TestRepairBasicRunHappy(c *C) {
  1476  	script := `#!/bin/sh
  1477  echo "happy output"
  1478  echo "done" >&$SNAP_REPAIR_STATUS_FD
  1479  exit 0
  1480  `
  1481  	s.seqRepairs = []string{makeMockRepair(script)}
  1482  	s.testScriptRun(c, script)
  1483  	// verify
  1484  	s.verifyRundir(c, []string{
  1485  		`^r0.done$`,
  1486  		`^r0.script$`,
  1487  		`^work$`,
  1488  	})
  1489  	s.verifyOutput(c, "r0.done", `repair: canonical-1
  1490  revision: 0
  1491  summary: repair one
  1492  output:
  1493  happy output
  1494  `)
  1495  	verifyRepairStatus(c, repair.DoneStatus)
  1496  }
  1497  
  1498  func (s *runScriptSuite) TestRepairBasicRunUnhappy(c *C) {
  1499  	script := `#!/bin/sh
  1500  echo "unhappy output"
  1501  exit 1
  1502  `
  1503  	s.seqRepairs = []string{makeMockRepair(script)}
  1504  	s.testScriptRun(c, script)
  1505  	// verify
  1506  	s.verifyRundir(c, []string{
  1507  		`^r0.retry$`,
  1508  		`^r0.script$`,
  1509  		`^work$`,
  1510  	})
  1511  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  1512  revision: 0
  1513  summary: repair one
  1514  output:
  1515  unhappy output
  1516  
  1517  repair canonical-1 revision 0 failed: exit status 1`)
  1518  	verifyRepairStatus(c, repair.RetryStatus)
  1519  
  1520  	c.Check(s.errReport.repair, Equals, "canonical/1")
  1521  	c.Check(s.errReport.errMsg, Equals, `repair canonical-1 revision 0 failed: exit status 1`)
  1522  	c.Check(s.errReport.dupSig, Equals, `canonical/1
  1523  repair canonical-1 revision 0 failed: exit status 1
  1524  output:
  1525  repair: canonical-1
  1526  revision: 0
  1527  summary: repair one
  1528  output:
  1529  unhappy output
  1530  `)
  1531  	c.Check(s.errReport.extra, DeepEquals, map[string]string{
  1532  		"Revision": "0",
  1533  		"RepairID": "1",
  1534  		"BrandID":  "canonical",
  1535  		"Status":   "retry",
  1536  	})
  1537  }
  1538  
  1539  func (s *runScriptSuite) TestRepairBasicSkip(c *C) {
  1540  	script := `#!/bin/sh
  1541  echo "other output"
  1542  echo "skip" >&$SNAP_REPAIR_STATUS_FD
  1543  exit 0
  1544  `
  1545  	s.seqRepairs = []string{makeMockRepair(script)}
  1546  	s.testScriptRun(c, script)
  1547  	// verify
  1548  	s.verifyRundir(c, []string{
  1549  		`^r0.script$`,
  1550  		`^r0.skip$`,
  1551  		`^work$`,
  1552  	})
  1553  	s.verifyOutput(c, "r0.skip", `repair: canonical-1
  1554  revision: 0
  1555  summary: repair one
  1556  output:
  1557  other output
  1558  `)
  1559  	verifyRepairStatus(c, repair.SkipStatus)
  1560  }
  1561  
  1562  func (s *runScriptSuite) TestRepairBasicRunUnhappyThenHappy(c *C) {
  1563  	script := `#!/bin/sh
  1564  if [ -f zzz-ran-once ]; then
  1565      echo "happy now"
  1566      echo "done" >&$SNAP_REPAIR_STATUS_FD
  1567      exit 0
  1568  fi
  1569  echo "unhappy output"
  1570  touch zzz-ran-once
  1571  exit 1
  1572  `
  1573  	s.seqRepairs = []string{makeMockRepair(script)}
  1574  	rpr := s.testScriptRun(c, script)
  1575  	s.verifyRundir(c, []string{
  1576  		`^r0.retry$`,
  1577  		`^r0.script$`,
  1578  		`^work$`,
  1579  	})
  1580  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  1581  revision: 0
  1582  summary: repair one
  1583  output:
  1584  unhappy output
  1585  
  1586  repair canonical-1 revision 0 failed: exit status 1`)
  1587  	verifyRepairStatus(c, repair.RetryStatus)
  1588  
  1589  	// run again, it will be happy this time
  1590  	err := rpr.Run()
  1591  	c.Assert(err, IsNil)
  1592  
  1593  	s.verifyRundir(c, []string{
  1594  		`^r0.done$`,
  1595  		`^r0.retry$`,
  1596  		`^r0.script$`,
  1597  		`^work$`,
  1598  	})
  1599  	s.verifyOutput(c, "r0.done", `repair: canonical-1
  1600  revision: 0
  1601  summary: repair one
  1602  output:
  1603  happy now
  1604  `)
  1605  	verifyRepairStatus(c, repair.DoneStatus)
  1606  }
  1607  
  1608  func (s *runScriptSuite) TestRepairHitsTimeout(c *C) {
  1609  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1610  	defer r1()
  1611  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1612  	defer r2()
  1613  
  1614  	restore := repair.MockDefaultRepairTimeout(100 * time.Millisecond)
  1615  	defer restore()
  1616  
  1617  	script := `#!/bin/sh
  1618  echo "output before timeout"
  1619  sleep 100
  1620  `
  1621  	s.seqRepairs = []string{makeMockRepair(script)}
  1622  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  1623  
  1624  	rpr, err := s.runner.Next("canonical")
  1625  	c.Assert(err, IsNil)
  1626  
  1627  	err = rpr.Run()
  1628  	c.Assert(err, IsNil)
  1629  
  1630  	s.verifyRundir(c, []string{
  1631  		`^r0.retry$`,
  1632  		`^r0.script$`,
  1633  		`^work$`,
  1634  	})
  1635  	s.verifyOutput(c, "r0.retry", `repair: canonical-1
  1636  revision: 0
  1637  summary: repair one
  1638  output:
  1639  output before timeout
  1640  
  1641  repair canonical-1 revision 0 failed: repair did not finish within 100ms`)
  1642  	verifyRepairStatus(c, repair.RetryStatus)
  1643  }
  1644  
  1645  func (s *runScriptSuite) TestRepairHasCorrectPath(c *C) {
  1646  	r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1647  	defer r1()
  1648  	r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
  1649  	defer r2()
  1650  
  1651  	script := `#!/bin/sh
  1652  echo PATH=$PATH
  1653  ls -l ${PATH##*:}/repair
  1654  `
  1655  	s.seqRepairs = []string{makeMockRepair(script)}
  1656  	s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
  1657  
  1658  	rpr, err := s.runner.Next("canonical")
  1659  	c.Assert(err, IsNil)
  1660  
  1661  	err = rpr.Run()
  1662  	c.Assert(err, IsNil)
  1663  
  1664  	c.Check(filepath.Join(s.runDir, "r0.retry"), testutil.FileMatches, fmt.Sprintf(`(?ms).*^PATH=.*:.*/run/snapd/repair/tools.*`))
  1665  	c.Check(filepath.Join(s.runDir, "r0.retry"), testutil.FileContains, `/repair -> /usr/lib/snapd/snap-repair`)
  1666  
  1667  	// run again and ensure no error happens
  1668  	err = rpr.Run()
  1669  	c.Assert(err, IsNil)
  1670  
  1671  }
  1672  
  1673  // shared1620RunnerSuite is embedded by runner16Suite and
  1674  // runner20Suite and the tests are run once with a simulated uc16 and
  1675  // once with a simulated uc20 environment
  1676  type shared1620RunnerSuite struct {
  1677  	baseRunnerSuite
  1678  
  1679  	writeSeedAssert func(c *C, fname string, a asserts.Assertion)
  1680  }
  1681  
  1682  func (s *shared1620RunnerSuite) TestTLSTime(c *C) {
  1683  	s.freshState(c)
  1684  	runner := repair.NewRunner()
  1685  	err := runner.LoadState()
  1686  	c.Assert(err, IsNil)
  1687  	epoch := time.Unix(0, 0)
  1688  	r := repair.MockTimeNow(func() time.Time {
  1689  		return epoch
  1690  	})
  1691  	defer r()
  1692  	c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true)
  1693  }
  1694  
  1695  func (s *shared1620RunnerSuite) TestLoadStateInitState(c *C) {
  1696  	// sanity
  1697  	c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false)
  1698  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false)
  1699  	// setup realistic seed/assertions
  1700  	r := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1701  	defer r()
  1702  	s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey(""))
  1703  	s.writeSeedAssert(c, "brand.account", s.brandAcct)
  1704  	s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey)
  1705  	s.writeSeedAssert(c, "model", s.modelAs)
  1706  
  1707  	runner := repair.NewRunner()
  1708  	err := runner.LoadState()
  1709  	c.Assert(err, IsNil)
  1710  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true)
  1711  
  1712  	brand, model := runner.BrandModel()
  1713  	c.Check(brand, Equals, "my-brand")
  1714  	c.Check(model, Equals, "my-model-2")
  1715  
  1716  	c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true)
  1717  }
  1718  
  1719  type runner16Suite struct {
  1720  	shared1620RunnerSuite
  1721  }
  1722  
  1723  var _ = Suite(&runner16Suite{})
  1724  
  1725  func (s *runner16Suite) SetUpTest(c *C) {
  1726  	s.shared1620RunnerSuite.SetUpTest(c)
  1727  
  1728  	s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions")
  1729  
  1730  	// dummy seed yaml
  1731  	err := os.MkdirAll(s.seedAssertsDir, 0755)
  1732  	c.Assert(err, IsNil)
  1733  	seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml")
  1734  	err = ioutil.WriteFile(seedYamlFn, nil, 0644)
  1735  	c.Assert(err, IsNil)
  1736  	seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z")
  1737  	c.Assert(err, IsNil)
  1738  	err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime)
  1739  	c.Assert(err, IsNil)
  1740  	s.seedTime = seedTime
  1741  
  1742  	s.t0 = time.Now().UTC().Truncate(time.Minute)
  1743  
  1744  	s.writeSeedAssert = s.writeSeedAssert16
  1745  }
  1746  
  1747  func (s *runner16Suite) writeSeedAssert16(c *C, fname string, a asserts.Assertion) {
  1748  	err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644)
  1749  	c.Assert(err, IsNil)
  1750  }
  1751  
  1752  func (s *runner16Suite) rmSeedAssert16(c *C, fname string) {
  1753  	err := os.Remove(filepath.Join(s.seedAssertsDir, fname))
  1754  	c.Assert(err, IsNil)
  1755  }
  1756  
  1757  func (s *runner16Suite) TestLoadStateInitDeviceInfoFail(c *C) {
  1758  	// sanity
  1759  	c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false)
  1760  	c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false)
  1761  	// setup realistic seed/assertions
  1762  	r := sysdb.InjectTrusted(s.storeSigning.Trusted)
  1763  	defer r()
  1764  
  1765  	const errPrefix = "cannot set device information: "
  1766  	tests := []struct {
  1767  		breakFunc   func()
  1768  		expectedErr string
  1769  	}{
  1770  		{func() { s.rmSeedAssert16(c, "model") }, errPrefix + "no model assertion in seed data"},
  1771  		{func() { s.rmSeedAssert16(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"},
  1772  		{func() { s.rmSeedAssert16(c, "brand.account-key") }, errPrefix + `cannot find public key.*`},
  1773  		{func() {
  1774  			// broken signature
  1775  			blob := asserts.Encode(s.brandAcct)
  1776  			err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, "brand.account"), blob[:len(blob)-3], 0644)
  1777  			c.Assert(err, IsNil)
  1778  		}, errPrefix + "cannot decode signature:.*"},
  1779  		{func() { s.writeSeedAssert(c, "model2", s.modelAs) }, errPrefix + "multiple models in seed assertions"},
  1780  	}
  1781  
  1782  	for _, test := range tests {
  1783  		s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey(""))
  1784  		s.writeSeedAssert(c, "brand.account", s.brandAcct)
  1785  		s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey)
  1786  		s.writeSeedAssert(c, "model", s.modelAs)
  1787  
  1788  		test.breakFunc()
  1789  
  1790  		runner := repair.NewRunner()
  1791  		err := runner.LoadState()
  1792  		c.Check(err, ErrorMatches, test.expectedErr)
  1793  	}
  1794  }
  1795  
  1796  type runner20Suite struct {
  1797  	shared1620RunnerSuite
  1798  }
  1799  
  1800  var _ = Suite(&runner20Suite{})
  1801  
  1802  var mockModeenv = []byte(`
  1803  mode=run
  1804  model=my-brand/my-model-2
  1805  `)
  1806  
  1807  func (s *runner20Suite) SetUpTest(c *C) {
  1808  	s.shared1620RunnerSuite.SetUpTest(c)
  1809  
  1810  	s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions")
  1811  	err := os.MkdirAll(s.seedAssertsDir, 0755)
  1812  	c.Assert(err, IsNil)
  1813  
  1814  	// write dummy modeenv
  1815  	err = os.MkdirAll(filepath.Dir(dirs.SnapModeenvFile), 0755)
  1816  	c.Assert(err, IsNil)
  1817  	err = ioutil.WriteFile(dirs.SnapModeenvFile, mockModeenv, 0644)
  1818  	c.Assert(err, IsNil)
  1819  	// validate that modeenv is actually valid
  1820  	_, err = boot.ReadModeenv("")
  1821  	c.Assert(err, IsNil)
  1822  
  1823  	seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z")
  1824  	c.Assert(err, IsNil)
  1825  	err = os.Chtimes(dirs.SnapModeenvFile, seedTime, seedTime)
  1826  	c.Assert(err, IsNil)
  1827  	s.seedTime = seedTime
  1828  	s.t0 = time.Now().UTC().Truncate(time.Minute)
  1829  
  1830  	s.writeSeedAssert = s.writeSeedAssert20
  1831  }
  1832  
  1833  func (s *runner20Suite) writeSeedAssert20(c *C, fname string, a asserts.Assertion) {
  1834  	var fn string
  1835  	if _, ok := a.(*asserts.Model); ok {
  1836  		fn = filepath.Join(s.seedAssertsDir, "../model")
  1837  	} else {
  1838  		fn = filepath.Join(s.seedAssertsDir, fname)
  1839  	}
  1840  	err := ioutil.WriteFile(fn, asserts.Encode(a), 0644)
  1841  	c.Assert(err, IsNil)
  1842  
  1843  	// ensure model assertion file has the correct seed time
  1844  	if _, ok := a.(*asserts.Model); ok {
  1845  		err = os.Chtimes(fn, s.seedTime, s.seedTime)
  1846  		c.Assert(err, IsNil)
  1847  	}
  1848  }
  1849  
  1850  func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvInvalidContent(c *C) {
  1851  	runner := repair.NewRunner()
  1852  
  1853  	for _, tc := range []struct {
  1854  		modelStr    string
  1855  		expectedErr string
  1856  	}{
  1857  		{
  1858  			`invalid-key-value`,
  1859  			"cannot set device information: No option model in section ",
  1860  		}, {
  1861  			`model=`,
  1862  			`cannot set device information: cannot find brand/model in modeenv model string ""`,
  1863  		}, {
  1864  			`model=brand-but-no-model`,
  1865  			`cannot set device information: cannot find brand/model in modeenv model string "brand-but-no-model"`,
  1866  		},
  1867  	} {
  1868  		err := ioutil.WriteFile(dirs.SnapModeenvFile, []byte(tc.modelStr), 0644)
  1869  		c.Assert(err, IsNil)
  1870  		err = runner.LoadState()
  1871  		c.Check(err, ErrorMatches, tc.expectedErr)
  1872  	}
  1873  }
  1874  
  1875  func (s *runner20Suite) TestLoadStateInitDeviceInfoModeenvIncorrectPermissions(c *C) {
  1876  	runner := repair.NewRunner()
  1877  
  1878  	err := os.Chmod(dirs.SnapModeenvFile, 0300)
  1879  	c.Assert(err, IsNil)
  1880  	s.AddCleanup(func() {
  1881  		err := os.Chmod(dirs.SnapModeenvFile, 0644)
  1882  		c.Assert(err, IsNil)
  1883  	})
  1884  	err = runner.LoadState()
  1885  	c.Check(err, ErrorMatches, "cannot set device information: open /.*/modeenv: permission denied")
  1886  }