github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/batch_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2021 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) TestCommitToAndObserve(c *C) {
   107  	b := &bytes.Buffer{}
   108  	enc := asserts.NewEncoder(b)
   109  	// wrong order is ok
   110  	err := enc.Encode(s.dev1Acct)
   111  	c.Assert(err, IsNil)
   112  	enc.Encode(s.storeSigning.StoreAccountKey(""))
   113  	c.Assert(err, IsNil)
   114  
   115  	batch := asserts.NewBatch(nil)
   116  	refs, err := batch.AddStream(b)
   117  	c.Assert(err, IsNil)
   118  	c.Check(refs, DeepEquals, []*asserts.Ref{
   119  		{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
   120  		{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
   121  	})
   122  
   123  	// noop
   124  	err = batch.Add(s.storeSigning.StoreAccountKey(""))
   125  	c.Assert(err, IsNil)
   126  
   127  	var seen []*asserts.Ref
   128  	obs := func(verified asserts.Assertion) {
   129  		seen = append(seen, verified.Ref())
   130  	}
   131  	err = batch.CommitToAndObserve(s.db, obs, nil)
   132  	c.Assert(err, IsNil)
   133  
   134  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   135  		"account-id": s.dev1Acct.AccountID(),
   136  	})
   137  	c.Assert(err, IsNil)
   138  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   139  
   140  	// this is the order they needed to be added
   141  	c.Check(seen, DeepEquals, []*asserts.Ref{
   142  		{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
   143  		{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
   144  	})
   145  }
   146  
   147  func (s *batchSuite) TestAddEmptyStream(c *C) {
   148  	b := &bytes.Buffer{}
   149  
   150  	batch := asserts.NewBatch(nil)
   151  	refs, err := batch.AddStream(b)
   152  	c.Assert(err, IsNil)
   153  	c.Check(refs, HasLen, 0)
   154  }
   155  
   156  func (s *batchSuite) TestConsiderPreexisting(c *C) {
   157  	// prereq store key
   158  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   159  	c.Assert(err, IsNil)
   160  
   161  	batch := asserts.NewBatch(nil)
   162  	err = batch.Add(s.dev1Acct)
   163  	c.Assert(err, IsNil)
   164  
   165  	err = batch.CommitTo(s.db, nil)
   166  	c.Assert(err, IsNil)
   167  
   168  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   169  		"account-id": s.dev1Acct.AccountID(),
   170  	})
   171  	c.Assert(err, IsNil)
   172  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   173  }
   174  
   175  func (s *batchSuite) TestAddStreamReturnsEffectivelyAddedRefs(c *C) {
   176  	batch := asserts.NewBatch(nil)
   177  
   178  	err := batch.Add(s.storeSigning.StoreAccountKey(""))
   179  	c.Assert(err, IsNil)
   180  
   181  	b := &bytes.Buffer{}
   182  	enc := asserts.NewEncoder(b)
   183  	// wrong order is ok
   184  	err = enc.Encode(s.dev1Acct)
   185  	c.Assert(err, IsNil)
   186  	// this was already added to the batch
   187  	enc.Encode(s.storeSigning.StoreAccountKey(""))
   188  	c.Assert(err, IsNil)
   189  
   190  	// effectively adds only the developer1 account
   191  	refs, err := batch.AddStream(b)
   192  	c.Assert(err, IsNil)
   193  	c.Check(refs, DeepEquals, []*asserts.Ref{
   194  		{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
   195  	})
   196  
   197  	err = batch.CommitTo(s.db, nil)
   198  	c.Assert(err, IsNil)
   199  
   200  	devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
   201  		"account-id": s.dev1Acct.AccountID(),
   202  	})
   203  	c.Assert(err, IsNil)
   204  	c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
   205  }
   206  
   207  func (s *batchSuite) TestCommitRefusesSelfSignedKey(c *C) {
   208  	aKey, _ := assertstest.GenerateKey(752)
   209  	aSignDB := assertstest.NewSigningDB("can0nical", aKey)
   210  
   211  	aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey())
   212  	c.Assert(err, IsNil)
   213  
   214  	headers := map[string]interface{}{
   215  		"authority-id":        "can0nical",
   216  		"account-id":          "can0nical",
   217  		"public-key-sha3-384": aKey.PublicKey().ID(),
   218  		"name":                "default",
   219  		"since":               time.Now().UTC().Format(time.RFC3339),
   220  	}
   221  	acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "")
   222  	c.Assert(err, IsNil)
   223  
   224  	headers = map[string]interface{}{
   225  		"authority-id": "can0nical",
   226  		"brand-id":     "can0nical",
   227  		"repair-id":    "2",
   228  		"summary":      "repair two",
   229  		"timestamp":    time.Now().UTC().Format(time.RFC3339),
   230  	}
   231  	repair, err := aSignDB.Sign(asserts.RepairType, headers, []byte("#script"), "")
   232  	c.Assert(err, IsNil)
   233  
   234  	batch := asserts.NewBatch(nil)
   235  
   236  	err = batch.Add(repair)
   237  	c.Assert(err, IsNil)
   238  
   239  	err = batch.Add(acctKey)
   240  	c.Assert(err, IsNil)
   241  
   242  	// this must fail
   243  	err = batch.CommitTo(s.db, nil)
   244  	c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`)
   245  }
   246  
   247  func (s *batchSuite) TestAddUnsupported(c *C) {
   248  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
   249  	defer restore()
   250  
   251  	batch := asserts.NewBatch(nil)
   252  
   253  	var a asserts.Assertion
   254  	(func() {
   255  		restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
   256  		defer restore()
   257  		headers := map[string]interface{}{
   258  			"format":       "999",
   259  			"revision":     "1",
   260  			"series":       "16",
   261  			"snap-id":      "snap-id-1",
   262  			"snap-name":    "foo",
   263  			"publisher-id": s.dev1Acct.AccountID(),
   264  			"timestamp":    time.Now().Format(time.RFC3339),
   265  		}
   266  		var err error
   267  		a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   268  		c.Assert(err, IsNil)
   269  	})()
   270  
   271  	err := batch.Add(a)
   272  	c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`)
   273  }
   274  
   275  func (s *batchSuite) TestAddUnsupportedIgnore(c *C) {
   276  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
   277  	defer restore()
   278  
   279  	var uRef *asserts.Ref
   280  	unsupported := func(ref *asserts.Ref, _ error) error {
   281  		uRef = ref
   282  		return nil
   283  	}
   284  
   285  	batch := asserts.NewBatch(unsupported)
   286  
   287  	var a asserts.Assertion
   288  	(func() {
   289  		restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
   290  		defer restore()
   291  		headers := map[string]interface{}{
   292  			"format":       "999",
   293  			"revision":     "1",
   294  			"series":       "16",
   295  			"snap-id":      "snap-id-1",
   296  			"snap-name":    "foo",
   297  			"publisher-id": s.dev1Acct.AccountID(),
   298  			"timestamp":    time.Now().Format(time.RFC3339),
   299  		}
   300  		var err error
   301  		a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   302  		c.Assert(err, IsNil)
   303  	})()
   304  
   305  	err := batch.Add(a)
   306  	c.Check(err, IsNil)
   307  	c.Check(uRef, DeepEquals, &asserts.Ref{
   308  		Type:       asserts.SnapDeclarationType,
   309  		PrimaryKey: []string{"16", "snap-id-1"},
   310  	})
   311  }
   312  
   313  func (s *batchSuite) TestCommitPartial(c *C) {
   314  	// Commit does add any successful assertion until the first error
   315  
   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  	err = batch.Add(s.dev1Acct)
   327  	c.Assert(err, IsNil)
   328  
   329  	// too old
   330  	rev := 1
   331  	headers := map[string]interface{}{
   332  		"snap-id":       "foo-id",
   333  		"snap-sha3-384": makeDigest(rev),
   334  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   335  		"snap-revision": fmt.Sprintf("%d", rev),
   336  		"developer-id":  s.dev1Acct.AccountID(),
   337  		"timestamp":     time.Time{}.Format(time.RFC3339),
   338  	}
   339  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   340  	c.Assert(err, IsNil)
   341  
   342  	err = batch.Add(snapRev)
   343  	c.Assert(err, IsNil)
   344  
   345  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: false})
   346  	c.Check(err, ErrorMatches, `(?ms).*validity.*`)
   347  
   348  	// snap-declaration was added anyway
   349  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   350  		"series":  "16",
   351  		"snap-id": "foo-id",
   352  	})
   353  	c.Assert(err, IsNil)
   354  }
   355  
   356  func (s *batchSuite) TestCommitMissing(c *C) {
   357  	// store key already present
   358  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   359  	c.Assert(err, IsNil)
   360  
   361  	batch := asserts.NewBatch(nil)
   362  
   363  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   364  
   365  	err = batch.Add(snapDeclFoo)
   366  	c.Assert(err, IsNil)
   367  
   368  	err = batch.CommitTo(s.db, nil)
   369  	c.Check(err, ErrorMatches, `cannot resolve prerequisite assertion: account.*`)
   370  }
   371  
   372  func (s *batchSuite) TestPrecheckPartial(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  	// too old
   387  	rev := 1
   388  	headers := map[string]interface{}{
   389  		"snap-id":       "foo-id",
   390  		"snap-sha3-384": makeDigest(rev),
   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.Time{}.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  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
   403  	c.Check(err, ErrorMatches, `(?ms).*validity.*`)
   404  
   405  	// nothing was added
   406  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   407  		"series":  "16",
   408  		"snap-id": "foo-id",
   409  	})
   410  	c.Assert(asserts.IsNotFound(err), Equals, true)
   411  }
   412  
   413  func (s *batchSuite) TestPrecheckHappy(c *C) {
   414  	// store key already present
   415  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   416  	c.Assert(err, IsNil)
   417  
   418  	batch := asserts.NewBatch(nil)
   419  
   420  	snapDeclFoo := s.snapDecl(c, "foo", nil)
   421  
   422  	err = batch.Add(snapDeclFoo)
   423  	c.Assert(err, IsNil)
   424  	err = batch.Add(s.dev1Acct)
   425  	c.Assert(err, IsNil)
   426  
   427  	rev := 1
   428  	revDigest := makeDigest(rev)
   429  	headers := map[string]interface{}{
   430  		"snap-id":       "foo-id",
   431  		"snap-sha3-384": revDigest,
   432  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   433  		"snap-revision": fmt.Sprintf("%d", rev),
   434  		"developer-id":  s.dev1Acct.AccountID(),
   435  		"timestamp":     time.Now().Format(time.RFC3339),
   436  	}
   437  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   438  	c.Assert(err, IsNil)
   439  
   440  	err = batch.Add(snapRev)
   441  	c.Assert(err, IsNil)
   442  
   443  	// test precheck on its own
   444  	err = batch.DoPrecheck(s.db)
   445  	c.Assert(err, IsNil)
   446  
   447  	// nothing was added yet
   448  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   449  		"series":  "16",
   450  		"snap-id": "foo-id",
   451  	})
   452  	c.Assert(asserts.IsNotFound(err), Equals, true)
   453  
   454  	// commit (with precheck)
   455  	err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
   456  	c.Assert(err, IsNil)
   457  
   458  	_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
   459  		"snap-sha3-384": revDigest,
   460  	})
   461  	c.Check(err, IsNil)
   462  }
   463  
   464  func (s *batchSuite) TestFetch(c *C) {
   465  	err := s.db.Add(s.storeSigning.StoreAccountKey(""))
   466  	c.Assert(err, IsNil)
   467  
   468  	s.snapDecl(c, "foo", nil)
   469  
   470  	rev := 10
   471  	revDigest := makeDigest(rev)
   472  	headers := map[string]interface{}{
   473  		"snap-id":       "foo-id",
   474  		"snap-sha3-384": revDigest,
   475  		"snap-size":     fmt.Sprintf("%d", len(fakeSnap(rev))),
   476  		"snap-revision": fmt.Sprintf("%d", rev),
   477  		"developer-id":  s.dev1Acct.AccountID(),
   478  		"timestamp":     time.Now().Format(time.RFC3339),
   479  	}
   480  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   481  	c.Assert(err, IsNil)
   482  
   483  	err = s.storeSigning.Add(snapRev)
   484  	c.Assert(err, IsNil)
   485  	ref := snapRev.Ref()
   486  
   487  	batch := asserts.NewBatch(nil)
   488  
   489  	// retrieve from storeSigning
   490  	retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
   491  		return ref.Resolve(s.storeSigning.Find)
   492  	}
   493  	// fetching the snap-revision
   494  	fetching := func(f asserts.Fetcher) error {
   495  		return f.Fetch(ref)
   496  	}
   497  
   498  	err = batch.Fetch(s.db, retrieve, fetching)
   499  	c.Assert(err, IsNil)
   500  
   501  	// nothing was added yet
   502  	_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
   503  		"series":  "16",
   504  		"snap-id": "foo-id",
   505  	})
   506  	c.Assert(asserts.IsNotFound(err), Equals, true)
   507  
   508  	// commit
   509  	err = batch.CommitTo(s.db, nil)
   510  	c.Assert(err, IsNil)
   511  
   512  	_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
   513  		"snap-sha3-384": revDigest,
   514  	})
   515  	c.Check(err, IsNil)
   516  }