mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
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
517 lines
14 KiB
Go
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)
|
|
}
|