github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/asserts/batch_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2019 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 asserts_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/asserts/assertstest"
    31  )
    32  
    33  type batchSuite struct {
    34  	storeSigning *assertstest.StoreStack
    35  	dev1Acct     *asserts.Account
    36  
    37  	db *asserts.Database
    38  }
    39  
    40  var _ = Suite(&batchSuite{})
    41  
    42  func (s *batchSuite) SetUpTest(c *C) {
    43  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    44  
    45  	s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
    46  	err := s.storeSigning.Add(s.dev1Acct)
    47  	c.Assert(err, IsNil)
    48  
    49  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    50  		Backstore: asserts.NewMemoryBackstore(),
    51  		Trusted:   s.storeSigning.Trusted,
    52  	})
    53  	c.Assert(err, IsNil)
    54  	s.db = db
    55  }
    56  
    57  func (s *batchSuite) snapDecl(c *C, name string, extraHeaders map[string]interface{}) *asserts.SnapDeclaration {
    58  	headers := map[string]interface{}{
    59  		"series":       "16",
    60  		"snap-id":      name + "-id",
    61  		"snap-name":    name,
    62  		"publisher-id": s.dev1Acct.AccountID(),
    63  		"timestamp":    time.Now().Format(time.RFC3339),
    64  	}
    65  	for h, v := range extraHeaders {
    66  		headers[h] = v
    67  	}
    68  	decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
    69  	c.Assert(err, IsNil)
    70  	err = s.storeSigning.Add(decl)
    71  	c.Assert(err, IsNil)
    72  	return decl.(*asserts.SnapDeclaration)
    73  }
    74  
    75  func (s *batchSuite) TestAddStream(c *C) {
    76  	b := &bytes.Buffer{}
    77  	enc := asserts.NewEncoder(b)
    78  	// wrong order is ok
    79  	err := enc.Encode(s.dev1Acct)
    80  	c.Assert(err, IsNil)
    81  	enc.Encode(s.storeSigning.StoreAccountKey(""))
    82  	c.Assert(err, IsNil)
    83  
    84  	batch := asserts.NewBatch(nil)
    85  	refs, err := batch.AddStream(b)
    86  	c.Assert(err, IsNil)
    87  	c.Check(refs, DeepEquals, []*asserts.Ref{
    88  		{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
    89  		{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
    90  	})
    91  
    92  	// noop
    93  	err = batch.Add(s.storeSigning.StoreAccountKey(""))
    94  	c.Assert(err, IsNil)
    95  
    96  	err = batch.CommitTo(s.db, nil)
    97  	c.Assert(err, IsNil)
    98  
    99  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   100  		"account-id": s.dev1Acct.AccountID(),
   101  	})
   102  	c.Assert(err, IsNil)
   103  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   104  }
   105  
   106  func (s *batchSuite) TestAddEmptyStream(c *C) {
   107  	b := &bytes.Buffer{}
   108  
   109  	batch := asserts.NewBatch(nil)
   110  	refs, err := batch.AddStream(b)
   111  	c.Assert(err, IsNil)
   112  	c.Check(refs, HasLen, 0)
   113  }
   114  
   115  func (s *batchSuite) TestConsiderPreexisting(c *C) {
   116  	// prereq store key
   117  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   118  	c.Assert(err, IsNil)
   119  
   120  	batch := asserts.NewBatch(nil)
   121  	err = batch.Add(s.dev1Acct)
   122  	c.Assert(err, IsNil)
   123  
   124  	err = batch.CommitTo(s.db, nil)
   125  	c.Assert(err, IsNil)
   126  
   127  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   128  		"account-id": s.dev1Acct.AccountID(),
   129  	})
   130  	c.Assert(err, IsNil)
   131  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   132  }
   133  
   134  func (s *batchSuite) TestAddStreamReturnsEffectivelyAddedRefs(c *C) {
   135  	batch := asserts.NewBatch(nil)
   136  
   137  	err := batch.Add(s.storeSigning.StoreAccountKey(""))
   138  	c.Assert(err, IsNil)
   139  
   140  	b := &bytes.Buffer{}
   141  	enc := asserts.NewEncoder(b)
   142  	// wrong order is ok
   143  	err = enc.Encode(s.dev1Acct)
   144  	c.Assert(err, IsNil)
   145  	// this was already added to the batch
   146  	enc.Encode(s.storeSigning.StoreAccountKey(""))
   147  	c.Assert(err, IsNil)
   148  
   149  	// effectively adds only the developer1 account
   150  	refs, err := batch.AddStream(b)
   151  	c.Assert(err, IsNil)
   152  	c.Check(refs, DeepEquals, []*asserts.Ref{
   153  		{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
   154  	})
   155  
   156  	err = batch.CommitTo(s.db, nil)
   157  	c.Assert(err, IsNil)
   158  
   159  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   160  		"account-id": s.dev1Acct.AccountID(),
   161  	})
   162  	c.Assert(err, IsNil)
   163  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   164  }
   165  
   166  func (s *batchSuite) TestCommitRefusesSelfSignedKey(c *C) {
   167  	aKey, _ := assertstest.GenerateKey(752)
   168  	aSignDB := assertstest.NewSigningDB("can0nical", aKey)
   169  
   170  	aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey())
   171  	c.Assert(err, IsNil)
   172  
   173  	headers := map[string]interface{}{
   174  		"authority-id":        "can0nical",
   175  		"account-id":          "can0nical",
   176  		"public-key-sha3-384": aKey.PublicKey().ID(),
   177  		"name":                "default",
   178  		"since":               time.Now().UTC().Format(time.RFC3339),
   179  	}
   180  	acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "")
   181  	c.Assert(err, IsNil)
   182  
   183  	headers = map[string]interface{}{
   184  		"authority-id": "can0nical",
   185  		"brand-id":     "can0nical",
   186  		"repair-id":    "2",
   187  		"summary":      "repair two",
   188  		"timestamp":    time.Now().UTC().Format(time.RFC3339),
   189  	}
   190  	repair, err := aSignDB.Sign(asserts.RepairType, headers, []byte("#script"), "")
   191  	c.Assert(err, IsNil)
   192  
   193  	batch := asserts.NewBatch(nil)
   194  
   195  	err = batch.Add(repair)
   196  	c.Assert(err, IsNil)
   197  
   198  	err = batch.Add(acctKey)
   199  	c.Assert(err, IsNil)
   200  
   201  	// this must fail
   202  	err = batch.CommitTo(s.db, nil)
   203  	c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`)
   204  }
   205  
   206  func (s *batchSuite) TestAddUnsupported(c *C) {
   207  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
   208  	defer restore()
   209  
   210  	batch := asserts.NewBatch(nil)
   211  
   212  	var a asserts.Assertion
   213  	(func() {
   214  		restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
   215  		defer restore()
   216  		headers := map[string]interface{}{
   217  			"format":       "999",
   218  			"revision":     "1",
   219  			"series":       "16",
   220  			"snap-id":      "snap-id-1",
   221  			"snap-name":    "foo",
   222  			"publisher-id": s.dev1Acct.AccountID(),
   223  			"timestamp":    time.Now().Format(time.RFC3339),
   224  		}
   225  		var err error
   226  		a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   227  		c.Assert(err, IsNil)
   228  	})()
   229  
   230  	err := batch.Add(a)
   231  	c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`)
   232  }
   233  
   234  func (s *batchSuite) TestAddUnsupportedIgnore(c *C) {
   235  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
   236  	defer restore()
   237  
   238  	var uRef *asserts.Ref
   239  	unsupported := func(ref *asserts.Ref, _ error) error {
   240  		uRef = ref
   241  		return nil
   242  	}
   243  
   244  	batch := asserts.NewBatch(unsupported)
   245  
   246  	var a asserts.Assertion
   247  	(func() {
   248  		restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
   249  		defer restore()
   250  		headers := map[string]interface{}{
   251  			"format":       "999",
   252  			"revision":     "1",
   253  			"series":       "16",
   254  			"snap-id":      "snap-id-1",
   255  			"snap-name":    "foo",
   256  			"publisher-id": s.dev1Acct.AccountID(),
   257  			"timestamp":    time.Now().Format(time.RFC3339),
   258  		}
   259  		var err error
   260  		a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   261  		c.Assert(err, IsNil)
   262  	})()
   263  
   264  	err := batch.Add(a)
   265  	c.Check(err, IsNil)
   266  	c.Check(uRef, DeepEquals, &asserts.Ref{
   267  		Type:       asserts.SnapDeclarationType,
   268  		PrimaryKey: []string{"16", "snap-id-1"},
   269  	})
   270  }
   271  
   272  func (s *batchSuite) TestCommitPartial(c *C) {
   273  	// Commit does add any successful assertion until the first error
   274  
   275  	// store key already present
   276  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   277  	c.Assert(err, IsNil)
   278  
   279  	batch := asserts.NewBatch(nil)
   280  
   281  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   282  
   283  	err = batch.Add(snapDeclFoo)
   284  	c.Assert(err, IsNil)
   285  	err = batch.Add(s.dev1Acct)
   286  	c.Assert(err, IsNil)
   287  
   288  	// too old
   289  	rev := 1
   290  	headers := map[string]interface{}{
   291  		"snap-id":       "foo-id",
   292  		"snap-sha3-384": makeDigest(rev),
   293  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   294  		"snap-revision": fmt.Sprintf("%d", rev),
   295  		"developer-id":  s.dev1Acct.AccountID(),
   296  		"timestamp":     time.Time{}.Format(time.RFC3339),
   297  	}
   298  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   299  	c.Assert(err, IsNil)
   300  
   301  	err = batch.Add(snapRev)
   302  	c.Assert(err, IsNil)
   303  
   304  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: false})
   305  	c.Check(err, ErrorMatches, `(?ms).*validity.*`)
   306  
   307  	// snap-declaration was added anyway
   308  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   309  		"series":  "16",
   310  		"snap-id": "foo-id",
   311  	})
   312  	c.Assert(err, IsNil)
   313  }
   314  
   315  func (s *batchSuite) TestCommitMissing(c *C) {
   316  	// store key already present
   317  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   318  	c.Assert(err, IsNil)
   319  
   320  	batch := asserts.NewBatch(nil)
   321  
   322  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   323  
   324  	err = batch.Add(snapDeclFoo)
   325  	c.Assert(err, IsNil)
   326  
   327  	err = batch.CommitTo(s.db, nil)
   328  	c.Check(err, ErrorMatches, `cannot resolve prerequisite assertion: account.*`)
   329  }
   330  
   331  func (s *batchSuite) TestPrecheckPartial(c *C) {
   332  	// store key already present
   333  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   334  	c.Assert(err, IsNil)
   335  
   336  	batch := asserts.NewBatch(nil)
   337  
   338  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   339  
   340  	err = batch.Add(snapDeclFoo)
   341  	c.Assert(err, IsNil)
   342  	err = batch.Add(s.dev1Acct)
   343  	c.Assert(err, IsNil)
   344  
   345  	// too old
   346  	rev := 1
   347  	headers := map[string]interface{}{
   348  		"snap-id":       "foo-id",
   349  		"snap-sha3-384": makeDigest(rev),
   350  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   351  		"snap-revision": fmt.Sprintf("%d", rev),
   352  		"developer-id":  s.dev1Acct.AccountID(),
   353  		"timestamp":     time.Time{}.Format(time.RFC3339),
   354  	}
   355  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   356  	c.Assert(err, IsNil)
   357  
   358  	err = batch.Add(snapRev)
   359  	c.Assert(err, IsNil)
   360  
   361  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
   362  	c.Check(err, ErrorMatches, `(?ms).*validity.*`)
   363  
   364  	// nothing was added
   365  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   366  		"series":  "16",
   367  		"snap-id": "foo-id",
   368  	})
   369  	c.Assert(asserts.IsNotFound(err), Equals, true)
   370  }
   371  
   372  func (s *batchSuite) TestPrecheckHappy(c *C) {
   373  	// store key already present
   374  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   375  	c.Assert(err, IsNil)
   376  
   377  	batch := asserts.NewBatch(nil)
   378  
   379  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   380  
   381  	err = batch.Add(snapDeclFoo)
   382  	c.Assert(err, IsNil)
   383  	err = batch.Add(s.dev1Acct)
   384  	c.Assert(err, IsNil)
   385  
   386  	rev := 1
   387  	revDigest := makeDigest(rev)
   388  	headers := map[string]interface{}{
   389  		"snap-id":       "foo-id",
   390  		"snap-sha3-384": revDigest,
   391  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   392  		"snap-revision": fmt.Sprintf("%d", rev),
   393  		"developer-id":  s.dev1Acct.AccountID(),
   394  		"timestamp":     time.Now().Format(time.RFC3339),
   395  	}
   396  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   397  	c.Assert(err, IsNil)
   398  
   399  	err = batch.Add(snapRev)
   400  	c.Assert(err, IsNil)
   401  
   402  	// test precheck on its own
   403  	err = batch.DoPrecheck(s.db)
   404  	c.Assert(err, IsNil)
   405  
   406  	// nothing was added yet
   407  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   408  		"series":  "16",
   409  		"snap-id": "foo-id",
   410  	})
   411  	c.Assert(asserts.IsNotFound(err), Equals, true)
   412  
   413  	// commit (with precheck)
   414  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
   415  	c.Assert(err, IsNil)
   416  
   417  	_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
   418  		"snap-sha3-384": revDigest,
   419  	})
   420  	c.Check(err, IsNil)
   421  }
   422  
   423  func (s *batchSuite) TestFetch(c *C) {
   424  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   425  	c.Assert(err, IsNil)
   426  
   427  	s.snapDecl(c, "foo", nil)
   428  
   429  	rev := 10
   430  	revDigest := makeDigest(rev)
   431  	headers := map[string]interface{}{
   432  		"snap-id":       "foo-id",
   433  		"snap-sha3-384": revDigest,
   434  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   435  		"snap-revision": fmt.Sprintf("%d", rev),
   436  		"developer-id":  s.dev1Acct.AccountID(),
   437  		"timestamp":     time.Now().Format(time.RFC3339),
   438  	}
   439  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   440  	c.Assert(err, IsNil)
   441  
   442  	err = s.storeSigning.Add(snapRev)
   443  	c.Assert(err, IsNil)
   444  	ref := snapRev.Ref()
   445  
   446  	batch := asserts.NewBatch(nil)
   447  
   448  	// retrieve from storeSigning
   449  	retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
   450  		return ref.Resolve(s.storeSigning.Find)
   451  	}
   452  	// fetching the snap-revision
   453  	fetching := func(f asserts.Fetcher) error {
   454  		return f.Fetch(ref)
   455  	}
   456  
   457  	err = batch.Fetch(s.db, retrieve, fetching)
   458  	c.Assert(err, IsNil)
   459  
   460  	// nothing was added yet
   461  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   462  		"series":  "16",
   463  		"snap-id": "foo-id",
   464  	})
   465  	c.Assert(asserts.IsNotFound(err), Equals, true)
   466  
   467  	// commit
   468  	err = batch.CommitTo(s.db, nil)
   469  	c.Assert(err, IsNil)
   470  
   471  	_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
   472  		"snap-sha3-384": revDigest,
   473  	})
   474  	c.Check(err, IsNil)
   475  }