Skip to content

Commit

Permalink
a/snapasserts, o/assertstate: implement validate-component task handler
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj committed May 17, 2024
1 parent 3bbaa66 commit 4c6ed5e
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 0 deletions.
30 changes: 30 additions & 0 deletions asserts/snapasserts/snapasserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,36 @@ func FetchSnapAssertions(f asserts.Fetcher, snapSHA3_384, provenance string) err
return f.Fetch(ref)
}

// FetchComponentAssertions fetches the assertions matching the information
// described in the given SideInfo and ComponentSideInfo using the given
// fetcher.
func FetchComponentAssertions(f asserts.Fetcher, si *snap.SideInfo, csi *snap.ComponentSideInfo, hash, provenance string) error {
// for now starting from the snap-resource-revision will get us all other relevant assertions
ref := &asserts.Ref{
Type: asserts.SnapResourceRevisionType,
PrimaryKey: []string{si.SnapID, csi.Component.ComponentName, hash},
}
if provenance != "" {
ref.PrimaryKey = append(ref.PrimaryKey, provenance)
}

if err := f.Fetch(ref); err != nil {
return err
}

// fetch the snap-resource-pair as well
ref = &asserts.Ref{
Type: asserts.SnapResourcePairType,
PrimaryKey: []string{si.SnapID, csi.Component.ComponentName, csi.Revision.String(), si.Revision.String()},
}

if provenance != "" {
ref.PrimaryKey = append(ref.PrimaryKey, provenance)
}

return f.Fetch(ref)
}

// FetchSnapDeclaration fetches the snap declaration and its prerequisites for the given snap id using the given fetcher.
func FetchSnapDeclaration(f asserts.Fetcher, snapID string) error {
ref := &asserts.Ref{
Expand Down
66 changes: 66 additions & 0 deletions overlord/assertstate/assertmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package assertstate

import (
"errors"
"fmt"

"gopkg.in/tomb.v2"
Expand All @@ -42,6 +43,7 @@ func Manager(s *state.State, runner *state.TaskRunner) (*AssertManager, error) {
delayedCrossMgrInit()

runner.AddHandler("validate-snap", doValidateSnap, nil)
runner.AddHandler("validate-component", doValidateComponent, nil)

db, err := sysdb.Open()
if err != nil {
Expand Down Expand Up @@ -153,3 +155,67 @@ func doValidateSnap(t *state.Task, _ *tomb.Tomb) error {
// TODO: set DeveloperID from assertions
return nil
}

func doValidateComponent(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()

compsup, snapsup, err := snapstate.TaskComponentSetup(t)
if err != nil {
return fmt.Errorf("internal error: cannot obtain snap setup: %s", err)
}

sha3_384, compSize, err := asserts.SnapFileSHA3_384(compsup.CompPath)
if err != nil {
return err
}

deviceCtx, err := snapstate.DeviceCtx(st, t, nil)
if err != nil {
return err
}

modelAs := deviceCtx.Model()

// TODO: i think that the provenance of the component should be the same as
// the snap's provenance
expectedProv := snapsup.ExpectedProvenance

err = doFetch(st, snapsup.UserID, deviceCtx, nil, func(f asserts.Fetcher) error {
if err := snapasserts.FetchComponentAssertions(f, snapsup.SideInfo, compsup.CompSideInfo, sha3_384, expectedProv); err != nil {
return err
}

// TODO: do we want this part here? it happens in doValidateSnap
// fetch store assertion if available
if modelAs.Store() != "" {
err := snapasserts.FetchStore(f, modelAs.Store())
if notFound, ok := err.(*asserts.NotFoundError); ok {
if notFound.Type != asserts.StoreType {
return err
}
} else if err != nil {
return err
}
}

return nil
})
if errors.Is(err, &asserts.NotFoundError{}) {
return fmt.Errorf("cannot find supported signatures to verify component %q and its hash (%v)", compsup.ComponentName(), err)
}
if err != nil {
return err
}

db := DB(st)
err = snapasserts.CrossCheckResource(compsup.ComponentName(), sha3_384, expectedProv, compSize, compsup.CompSideInfo, snapsup.SideInfo, modelAs, db)
if err != nil {
return err
}

// TODO: do we need to do something like snapasserts.CheckProvenanceWithVerifiedRevision for components?

return nil
}
112 changes: 112 additions & 0 deletions overlord/assertstate/assertstate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"testing"
"time"
Expand All @@ -48,6 +49,7 @@ import (
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/naming"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/store/storetest"
Expand Down Expand Up @@ -566,6 +568,53 @@ func (s *assertMgrSuite) prereqSnapAssertions(c *C, revisions ...int) (paths map
return paths, digests
}

func (s *assertMgrSuite) prereqComponentAssertions(c *C, snapRev, compRev snap.Revision) (compPath string, digest string) {
const (
resourceName = "test-component"
snapID = "snap-id-1"
componentYaml = `component: snap+test-component
type: test
version: 1.0.2
`
)

compPath = snaptest.MakeTestComponentWithFiles(c, resourceName+".comp", componentYaml, nil)

digest, size, err := asserts.SnapFileSHA3_384(compPath)
c.Assert(err, IsNil)

revHeaders := map[string]interface{}{
"snap-id": snapID,
"resource-name": resourceName,
"resource-sha3-384": digest,
"resource-revision": compRev.String(),
"resource-size": strconv.Itoa(int(size)),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}

resourceRev, err := s.storeSigning.Sign(asserts.SnapResourceRevisionType, revHeaders, nil, "")
c.Assert(err, IsNil)
err = s.storeSigning.Add(resourceRev)
c.Assert(err, IsNil)

pairHeaders := map[string]interface{}{
"snap-id": snapID,
"resource-name": resourceName,
"resource-revision": compRev.String(),
"snap-revision": snapRev.String(),
"developer-id": s.dev1Acct.AccountID(),
"timestamp": time.Now().Format(time.RFC3339),
}

resourcePair, err := s.storeSigning.Sign(asserts.SnapResourcePairType, pairHeaders, nil, "")
c.Assert(err, IsNil)
err = s.storeSigning.Add(resourcePair)
c.Assert(err, IsNil)

return compPath, digest
}

func (s *assertMgrSuite) TestDoFetch(c *C) {
_, digests := s.prereqSnapAssertions(c, 10)

Expand Down Expand Up @@ -5216,3 +5265,66 @@ func (s *assertMgrSuite) TestAspectBundle(c *C) {
c.Check(bundle.Name, Equals, "foo")
c.Check(bundle.Schema, NotNil)
}

func (s *assertMgrSuite) TestValidateComponent(c *C) {
snapRev, compRev := snap.R(10), snap.R(20)

paths, _ := s.prereqSnapAssertions(c, 10)
snapPath := paths[10]

compPath, compDigest := s.prereqComponentAssertions(c, snapRev, compRev)

s.state.Lock()
defer s.state.Unlock()

// have a model and the store assertion available
storeAs := s.setupModelAndStore(c)
err := s.storeSigning.Add(storeAs)
c.Assert(err, IsNil)

chg := s.state.NewChange("install", "...")
t := s.state.NewTask("validate-component", "Fetch and check snap assertions")
snapsup := snapstate.SnapSetup{
SnapPath: snapPath,
UserID: 0,
SideInfo: &snap.SideInfo{
RealName: "foo",
SnapID: "snap-id-1",
Revision: snapRev,
},
}
compsup := snapstate.ComponentSetup{
CompPath: compPath,
CompSideInfo: &snap.ComponentSideInfo{
Component: naming.NewComponentRef("foo", "test-component"),
Revision: compRev,
},
}
t.Set("snap-setup", snapsup)
t.Set("component-setup", compsup)
chg.AddTask(t)

s.state.Unlock()
defer s.se.Stop()
s.settle(c)
s.state.Lock()

c.Assert(chg.Err(), IsNil)

db := assertstate.DB(s.state)

a, err := db.Find(asserts.SnapResourceRevisionType, map[string]string{
"resource-sha3-384": compDigest,
"resource-name": "test-component",
"snap-id": "snap-id-1",
})
c.Assert(err, IsNil)
c.Check(a.(*asserts.SnapResourceRevision).ResourceRevision(), Equals, 20)

// TODO: remove this check if we decide we don't need it
// store assertion was also fetched
_, err = db.Find(asserts.StoreType, map[string]string{
"store": "my-brand-store",
})
c.Assert(err, IsNil)
}

0 comments on commit 4c6ed5e

Please sign in to comment.