github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/seed/seed.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-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 seed implements loading and validating of seed data.
    21  package seed
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/seed/internal"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/timings"
    33  )
    34  
    35  var (
    36  	ErrNoAssertions = errors.New("no seed assertions")
    37  	ErrNoMeta       = errors.New("no seed metadata")
    38  )
    39  
    40  // Snap holds the details of a snap in a seed.
    41  type Snap struct {
    42  	Path string
    43  
    44  	SideInfo *snap.SideInfo
    45  
    46  	// EssentialType is the type of the snap as specified by the model.
    47  	// Provided only for essential snaps (Essential = true).
    48  	EssentialType snap.Type
    49  
    50  	Essential bool
    51  	Required  bool
    52  
    53  	// options
    54  	Channel string
    55  	DevMode bool
    56  	Classic bool
    57  }
    58  
    59  func (s *Snap) SnapName() string {
    60  	return s.SideInfo.RealName
    61  }
    62  
    63  func (s *Snap) ID() string {
    64  	return s.SideInfo.SnapID
    65  }
    66  
    67  // PlaceInfo returns a PlaceInfo for the seed snap.
    68  func (s *Snap) PlaceInfo() snap.PlaceInfo {
    69  	return &snap.Info{SideInfo: *s.SideInfo}
    70  }
    71  
    72  // Seed supports loading assertions and seed snaps' metadata.
    73  type Seed interface {
    74  	// LoadAssertions loads all assertions from the seed with
    75  	// cross-checks.  A read-only view on an assertions database
    76  	// can be passed in together with a commitTo function which
    77  	// will be used to commit the assertions to the underlying
    78  	// database. If db is nil an internal temporary database will
    79  	// be setup instead. ErrNoAssertions will be returned if there
    80  	// is no assertions directory in the seed, this is legitimate
    81  	// only on classic.
    82  	LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error
    83  
    84  	// Model returns the seed provided model assertion.
    85  	// It will panic if called before LoadAssertions.
    86  	Model() *asserts.Model
    87  
    88  	// Brand returns the brand information of the seed.
    89  	// It will panic if called before LoadAssertions.
    90  	Brand() (*asserts.Account, error)
    91  
    92  	// LoadEssentialMeta loads the seed's snaps metadata for the
    93  	// essential snaps with types in the essentialTypes set while
    94  	// verifying them against assertions. It can return ErrNoMeta
    95  	// if there is no metadata nor snaps in the seed, this is
    96  	// legitimate only on classic. It can be called multiple times
    97  	// if needed before invoking LoadMeta.
    98  	// It will panic if called before LoadAssertions.
    99  	LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error
   100  
   101  	// LoadMeta loads the seed and seed's snaps metadata while
   102  	// verifying the underlying snaps against assertions. It can
   103  	// return ErrNoMeta if there is no metadata nor snaps in the
   104  	// seed, this is legitimate only on classic.
   105  	// It will panic if called before LoadAssertions.
   106  	LoadMeta(tm timings.Measurer) error
   107  
   108  	// UsesSnapdSnap returns whether the system as defined by the
   109  	// seed will use the snapd snap, after LoadMeta.
   110  	UsesSnapdSnap() bool
   111  
   112  	// EssentialSnaps returns the essential snaps as defined by
   113  	// the seed, after LoadMeta.
   114  	EssentialSnaps() []*Snap
   115  
   116  	// ModeSnaps returns the snaps that should be available
   117  	// in the given mode as defined by the seed, after LoadMeta.
   118  	ModeSnaps(mode string) ([]*Snap, error)
   119  }
   120  
   121  // Open returns a Seed implementation for the seed at seedDir.
   122  // label if not empty is used to identify a Core 20 recovery system seed.
   123  func Open(seedDir, label string) (Seed, error) {
   124  	if label != "" {
   125  		if err := internal.ValidateUC20SeedSystemLabel(label); err != nil {
   126  			return nil, err
   127  		}
   128  		return &seed20{systemDir: filepath.Join(seedDir, "systems", label)}, nil
   129  	}
   130  	return &seed16{seedDir: seedDir}, nil
   131  }
   132  
   133  // ReadSystemEssential retrieves in one go information about the model
   134  // and essential snaps of the given types for the Core 20 recovery
   135  // system seed specified by seedDir and label (which cannot be empty).
   136  func ReadSystemEssential(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*Snap, error) {
   137  	if label == "" {
   138  		return nil, nil, fmt.Errorf("system label cannot be empty")
   139  	}
   140  	seed20, err := Open(seedDir, label)
   141  	if err != nil {
   142  		return nil, nil, err
   143  	}
   144  
   145  	// load assertions into a temporary database
   146  	if err := seed20.LoadAssertions(nil, nil); err != nil {
   147  		return nil, nil, err
   148  	}
   149  
   150  	// load and verify info about essential snaps
   151  	if err := seed20.LoadEssentialMeta(essentialTypes, tm); err != nil {
   152  		return nil, nil, err
   153  	}
   154  
   155  	return seed20.Model(), seed20.EssentialSnaps(), nil
   156  }
   157  
   158  // ReadSystemEssentialAndBetterEarliestTime retrieves in one go
   159  // information about the model and essential snaps of the given types
   160  // for the Core 20 recovery system seed specified by seedDir and label
   161  // (which cannot be empty).
   162  // It can operate even if current system time is unreliable by taking
   163  // a earliestTime lower bound for current time.
   164  // It returns as well an improved lower bound by considering
   165  // appropriate assertions in the seed.
   166  func ReadSystemEssentialAndBetterEarliestTime(seedDir, label string, essentialTypes []snap.Type, earliestTime time.Time, tm timings.Measurer) (*asserts.Model, []*Snap, time.Time, error) {
   167  	if label == "" {
   168  		return nil, nil, time.Time{}, fmt.Errorf("system label cannot be empty")
   169  	}
   170  	seed20, err := Open(seedDir, label)
   171  	if err != nil {
   172  		return nil, nil, time.Time{}, err
   173  
   174  	}
   175  
   176  	improve := func(a asserts.Assertion) {
   177  		// we consider only snap-revision and snap-declaration
   178  		// assertions here as they must be store-signed, see
   179  		// checkConsistency for each type
   180  		// other assertions might be signed not by the store
   181  		// nor the brand so they might be provided by an
   182  		// attacker, signed using a registered key but
   183  		// containing unreliable time
   184  		var tstamp time.Time
   185  		switch a.Type() {
   186  		default:
   187  			// not one of the store-signed assertion types
   188  			return
   189  		case asserts.SnapRevisionType:
   190  			sr := a.(*asserts.SnapRevision)
   191  			tstamp = sr.Timestamp()
   192  		case asserts.SnapDeclarationType:
   193  			sd := a.(*asserts.SnapDeclaration)
   194  			tstamp = sd.Timestamp()
   195  		}
   196  		if tstamp.After(earliestTime) {
   197  			earliestTime = tstamp
   198  		}
   199  	}
   200  
   201  	// create a temporary database, commitTo will invoke improve
   202  	db, commitTo, err := newMemAssertionsDB(improve)
   203  	if err != nil {
   204  		return nil, nil, time.Time{}, err
   205  	}
   206  	// set up the database to check for key expiry only assuming
   207  	// earliestTime (if not zero)
   208  	db.SetEarliestTime(earliestTime)
   209  
   210  	// load assertions into the temporary database
   211  	if err := seed20.LoadAssertions(db, commitTo); err != nil {
   212  		return nil, nil, time.Time{}, err
   213  	}
   214  
   215  	// load and verify info about essential snaps
   216  	if err := seed20.LoadEssentialMeta(essentialTypes, tm); err != nil {
   217  		return nil, nil, time.Time{}, err
   218  	}
   219  
   220  	// consider the model's timestamp as well - it must be signed
   221  	// by the brand so is safe from the attack detailed above
   222  	mod := seed20.Model()
   223  	if mod.Timestamp().After(earliestTime) {
   224  		earliestTime = mod.Timestamp()
   225  	}
   226  
   227  	return mod, seed20.EssentialSnaps(), earliestTime, nil
   228  }