Files
snapd/asserts/batch_test.go
Samuele Pedroni 35c76d6f1a seed: ReadSystemEssentialAndBetterEarliestTime (#10005)
ReadSystemEssentialAndBetterEarliestTime retrieves in one go
information about the model and essential snaps of the given types
for the Core 20 recovery system seed specified by seedDir and label
(which cannot be empty).
It can operate even if current system time is unreliable by taking
a earliestTime lower bound for current time.
It returns as well an improved lower bound by considering appropriate
assertions in the seed.

* asserts: Batch.CommitToAndObserve

have a variant of CommitTo that supports a callback to consider each
assertion immediately after it has been added to the database, at
which point it also verified
2021-03-18 18:40:16 +01:00

517 lines
14 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016-2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package asserts_test
import (
"bytes"
"fmt"
"time"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
)
type batchSuite struct {
storeSigning *assertstest.StoreStack
dev1Acct *asserts.Account
db *asserts.Database
}
var _ = Suite(&batchSuite{})
func (s *batchSuite) SetUpTest(c *C) {
s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
err := s.storeSigning.Add(s.dev1Acct)
c.Assert(err, IsNil)
db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
Backstore: asserts.NewMemoryBackstore(),
Trusted: s.storeSigning.Trusted,
})
c.Assert(err, IsNil)
s.db = db
}
func (s *batchSuite) snapDecl(c *C, name string, extraHeaders map[string]interface{}) *asserts.SnapDeclaration {
headers := map[string]interface{}{
"series": "16",
"snap-id": name + "-id",
"snap-name": name,
"publisher-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}
for h, v := range extraHeaders {
headers[h] = v
}
decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
c.Assert(err, IsNil)
err = s.storeSigning.Add(decl)
c.Assert(err, IsNil)
return decl.(*asserts.SnapDeclaration)
}
func (s *batchSuite) TestAddStream(c *C) {
b := &bytes.Buffer{}
enc := asserts.NewEncoder(b)
// wrong order is ok
err := enc.Encode(s.dev1Acct)
c.Assert(err, IsNil)
enc.Encode(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
refs, err := batch.AddStream(b)
c.Assert(err, IsNil)
c.Check(refs, DeepEquals, []*asserts.Ref{
{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
})
// noop
err = batch.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
err = batch.CommitTo(s.db, nil)
c.Assert(err, IsNil)
devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
"account-id": s.dev1Acct.AccountID(),
})
c.Assert(err, IsNil)
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
}
func (s *batchSuite) TestCommitToAndObserve(c *C) {
b := &bytes.Buffer{}
enc := asserts.NewEncoder(b)
// wrong order is ok
err := enc.Encode(s.dev1Acct)
c.Assert(err, IsNil)
enc.Encode(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
refs, err := batch.AddStream(b)
c.Assert(err, IsNil)
c.Check(refs, DeepEquals, []*asserts.Ref{
{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
})
// noop
err = batch.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
var seen []*asserts.Ref
obs := func(verified asserts.Assertion) {
seen = append(seen, verified.Ref())
}
err = batch.CommitToAndObserve(s.db, obs, nil)
c.Assert(err, IsNil)
devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
"account-id": s.dev1Acct.AccountID(),
})
c.Assert(err, IsNil)
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
// this is the order they needed to be added
c.Check(seen, DeepEquals, []*asserts.Ref{
{Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
})
}
func (s *batchSuite) TestAddEmptyStream(c *C) {
b := &bytes.Buffer{}
batch := asserts.NewBatch(nil)
refs, err := batch.AddStream(b)
c.Assert(err, IsNil)
c.Check(refs, HasLen, 0)
}
func (s *batchSuite) TestConsiderPreexisting(c *C) {
// prereq store key
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
err = batch.Add(s.dev1Acct)
c.Assert(err, IsNil)
err = batch.CommitTo(s.db, nil)
c.Assert(err, IsNil)
devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
"account-id": s.dev1Acct.AccountID(),
})
c.Assert(err, IsNil)
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
}
func (s *batchSuite) TestAddStreamReturnsEffectivelyAddedRefs(c *C) {
batch := asserts.NewBatch(nil)
err := batch.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
b := &bytes.Buffer{}
enc := asserts.NewEncoder(b)
// wrong order is ok
err = enc.Encode(s.dev1Acct)
c.Assert(err, IsNil)
// this was already added to the batch
enc.Encode(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
// effectively adds only the developer1 account
refs, err := batch.AddStream(b)
c.Assert(err, IsNil)
c.Check(refs, DeepEquals, []*asserts.Ref{
{Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
})
err = batch.CommitTo(s.db, nil)
c.Assert(err, IsNil)
devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
"account-id": s.dev1Acct.AccountID(),
})
c.Assert(err, IsNil)
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
}
func (s *batchSuite) TestCommitRefusesSelfSignedKey(c *C) {
aKey, _ := assertstest.GenerateKey(752)
aSignDB := assertstest.NewSigningDB("can0nical", aKey)
aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey())
c.Assert(err, IsNil)
headers := map[string]interface{}{
"authority-id": "can0nical",
"account-id": "can0nical",
"public-key-sha3-384": aKey.PublicKey().ID(),
"name": "default",
"since": time.Now().UTC().Format(time.RFC3339),
}
acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "")
c.Assert(err, IsNil)
headers = map[string]interface{}{
"authority-id": "can0nical",
"brand-id": "can0nical",
"repair-id": "2",
"summary": "repair two",
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
repair, err := aSignDB.Sign(asserts.RepairType, headers, []byte("#script"), "")
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
err = batch.Add(repair)
c.Assert(err, IsNil)
err = batch.Add(acctKey)
c.Assert(err, IsNil)
// this must fail
err = batch.CommitTo(s.db, nil)
c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`)
}
func (s *batchSuite) TestAddUnsupported(c *C) {
restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
defer restore()
batch := asserts.NewBatch(nil)
var a asserts.Assertion
(func() {
restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
defer restore()
headers := map[string]interface{}{
"format": "999",
"revision": "1",
"series": "16",
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}
var err error
a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
c.Assert(err, IsNil)
})()
err := batch.Add(a)
c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`)
}
func (s *batchSuite) TestAddUnsupportedIgnore(c *C) {
restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
defer restore()
var uRef *asserts.Ref
unsupported := func(ref *asserts.Ref, _ error) error {
uRef = ref
return nil
}
batch := asserts.NewBatch(unsupported)
var a asserts.Assertion
(func() {
restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
defer restore()
headers := map[string]interface{}{
"format": "999",
"revision": "1",
"series": "16",
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}
var err error
a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
c.Assert(err, IsNil)
})()
err := batch.Add(a)
c.Check(err, IsNil)
c.Check(uRef, DeepEquals, &asserts.Ref{
Type: asserts.SnapDeclarationType,
PrimaryKey: []string{"16", "snap-id-1"},
})
}
func (s *batchSuite) TestCommitPartial(c *C) {
// Commit does add any successful assertion until the first error
// store key already present
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
err = batch.Add(snapDeclFoo)
c.Assert(err, IsNil)
err = batch.Add(s.dev1Acct)
c.Assert(err, IsNil)
// too old
rev := 1
headers := map[string]interface{}{
"snap-id": "foo-id",
"snap-sha3-384": makeDigest(rev),
"snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
"snap-revision": fmt.Sprintf("%d", rev),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Time{}.Format(time.RFC3339),
}
snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
c.Assert(err, IsNil)
err = batch.Add(snapRev)
c.Assert(err, IsNil)
err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: false})
c.Check(err, ErrorMatches, `(?ms).*validity.*`)
// snap-declaration was added anyway
_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
"series": "16",
"snap-id": "foo-id",
})
c.Assert(err, IsNil)
}
func (s *batchSuite) TestCommitMissing(c *C) {
// store key already present
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
err = batch.Add(snapDeclFoo)
c.Assert(err, IsNil)
err = batch.CommitTo(s.db, nil)
c.Check(err, ErrorMatches, `cannot resolve prerequisite assertion: account.*`)
}
func (s *batchSuite) TestPrecheckPartial(c *C) {
// store key already present
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
err = batch.Add(snapDeclFoo)
c.Assert(err, IsNil)
err = batch.Add(s.dev1Acct)
c.Assert(err, IsNil)
// too old
rev := 1
headers := map[string]interface{}{
"snap-id": "foo-id",
"snap-sha3-384": makeDigest(rev),
"snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
"snap-revision": fmt.Sprintf("%d", rev),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Time{}.Format(time.RFC3339),
}
snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
c.Assert(err, IsNil)
err = batch.Add(snapRev)
c.Assert(err, IsNil)
err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
c.Check(err, ErrorMatches, `(?ms).*validity.*`)
// nothing was added
_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
"series": "16",
"snap-id": "foo-id",
})
c.Assert(asserts.IsNotFound(err), Equals, true)
}
func (s *batchSuite) TestPrecheckHappy(c *C) {
// store key already present
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
err = batch.Add(snapDeclFoo)
c.Assert(err, IsNil)
err = batch.Add(s.dev1Acct)
c.Assert(err, IsNil)
rev := 1
revDigest := makeDigest(rev)
headers := map[string]interface{}{
"snap-id": "foo-id",
"snap-sha3-384": revDigest,
"snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
"snap-revision": fmt.Sprintf("%d", rev),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}
snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
c.Assert(err, IsNil)
err = batch.Add(snapRev)
c.Assert(err, IsNil)
// test precheck on its own
err = batch.DoPrecheck(s.db)
c.Assert(err, IsNil)
// nothing was added yet
_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
"series": "16",
"snap-id": "foo-id",
})
c.Assert(asserts.IsNotFound(err), Equals, true)
// commit (with precheck)
err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
c.Assert(err, IsNil)
_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
"snap-sha3-384": revDigest,
})
c.Check(err, IsNil)
}
func (s *batchSuite) TestFetch(c *C) {
err := s.db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
s.snapDecl(c, "foo", nil)
rev := 10
revDigest := makeDigest(rev)
headers := map[string]interface{}{
"snap-id": "foo-id",
"snap-sha3-384": revDigest,
"snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
"snap-revision": fmt.Sprintf("%d", rev),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}
snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
c.Assert(err, IsNil)
err = s.storeSigning.Add(snapRev)
c.Assert(err, IsNil)
ref := snapRev.Ref()
batch := asserts.NewBatch(nil)
// retrieve from storeSigning
retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
return ref.Resolve(s.storeSigning.Find)
}
// fetching the snap-revision
fetching := func(f asserts.Fetcher) error {
return f.Fetch(ref)
}
err = batch.Fetch(s.db, retrieve, fetching)
c.Assert(err, IsNil)
// nothing was added yet
_, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
"series": "16",
"snap-id": "foo-id",
})
c.Assert(asserts.IsNotFound(err), Equals, true)
// commit
err = batch.CommitTo(s.db, nil)
c.Assert(err, IsNil)
_, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
"snap-sha3-384": revDigest,
})
c.Check(err, IsNil)
}