github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/model.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2020 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
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/snapcore/snapd/snap/channel"
    29  	"github.com/snapcore/snapd/snap/naming"
    30  	"github.com/snapcore/snapd/strutil"
    31  )
    32  
    33  // TODO: for ModelSnap
    34  //  * consider moving snap.Type out of snap and using it in ModelSnap
    35  //    but remember assertions use "core" (never "os") for TypeOS
    36  //  * consider having a first-class Presence type
    37  
    38  // ModelSnap holds the details about a snap specified by a model assertion.
    39  type ModelSnap struct {
    40  	Name   string
    41  	SnapID string
    42  	// SnapType is one of: app|base|gadget|kernel|core, default is app
    43  	SnapType string
    44  	// Modes in which the snap must be made available
    45  	Modes []string
    46  	// DefaultChannel is the initial tracking channel,
    47  	// default is latest/stable in an extended model
    48  	DefaultChannel string
    49  	// PinnedTrack is a pinned track for the snap, if set DefaultChannel
    50  	// cannot be set at the same time (Core 18 models feature)
    51  	PinnedTrack string
    52  	// Presence is one of: required|optional
    53  	Presence string
    54  }
    55  
    56  // SnapName implements naming.SnapRef.
    57  func (s *ModelSnap) SnapName() string {
    58  	return s.Name
    59  }
    60  
    61  // ID implements naming.SnapRef.
    62  func (s *ModelSnap) ID() string {
    63  	return s.SnapID
    64  }
    65  
    66  type modelSnaps struct {
    67  	snapd            *ModelSnap
    68  	base             *ModelSnap
    69  	gadget           *ModelSnap
    70  	kernel           *ModelSnap
    71  	snapsNoEssential []*ModelSnap
    72  }
    73  
    74  func (ms *modelSnaps) list() (allSnaps []*ModelSnap, requiredWithEssentialSnaps []naming.SnapRef, numEssentialSnaps int) {
    75  	addSnap := func(snap *ModelSnap, essentialSnap int) {
    76  		if snap == nil {
    77  			return
    78  		}
    79  		numEssentialSnaps += essentialSnap
    80  		allSnaps = append(allSnaps, snap)
    81  		if snap.Presence == "required" {
    82  			requiredWithEssentialSnaps = append(requiredWithEssentialSnaps, snap)
    83  		}
    84  	}
    85  
    86  	addSnap(ms.snapd, 1)
    87  	addSnap(ms.kernel, 1)
    88  	addSnap(ms.base, 1)
    89  	addSnap(ms.gadget, 1)
    90  	for _, snap := range ms.snapsNoEssential {
    91  		addSnap(snap, 0)
    92  	}
    93  	return allSnaps, requiredWithEssentialSnaps, numEssentialSnaps
    94  }
    95  
    96  var (
    97  	essentialSnapModes = []string{"run", "ephemeral"}
    98  	defaultModes       = []string{"run"}
    99  )
   100  
   101  func checkExtendedSnaps(extendedSnaps interface{}, base string, grade ModelGrade) (*modelSnaps, error) {
   102  	const wrongHeaderType = `"snaps" header must be a list of maps`
   103  
   104  	entries, ok := extendedSnaps.([]interface{})
   105  	if !ok {
   106  		return nil, fmt.Errorf(wrongHeaderType)
   107  	}
   108  
   109  	var modelSnaps modelSnaps
   110  	seen := make(map[string]bool, len(entries))
   111  	seenIDs := make(map[string]string, len(entries))
   112  
   113  	for _, entry := range entries {
   114  		snap, ok := entry.(map[string]interface{})
   115  		if !ok {
   116  			return nil, fmt.Errorf(wrongHeaderType)
   117  		}
   118  		modelSnap, err := checkModelSnap(snap, grade)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  
   123  		if seen[modelSnap.Name] {
   124  			return nil, fmt.Errorf("cannot list the same snap %q multiple times", modelSnap.Name)
   125  		}
   126  		seen[modelSnap.Name] = true
   127  		// at this time we do not support parallel installing
   128  		// from model/seed
   129  		if snapID := modelSnap.SnapID; snapID != "" {
   130  			if underName := seenIDs[snapID]; underName != "" {
   131  				return nil, fmt.Errorf("cannot specify the same snap id %q multiple times, specified for snaps %q and %q", snapID, underName, modelSnap.Name)
   132  			}
   133  			seenIDs[snapID] = modelSnap.Name
   134  		}
   135  
   136  		essential := false
   137  		switch {
   138  		case modelSnap.SnapType == "snapd":
   139  			// TODO: allow to be explicit only in grade: dangerous?
   140  			essential = true
   141  			if modelSnaps.snapd != nil {
   142  				return nil, fmt.Errorf("cannot specify multiple snapd snaps: %q and %q", modelSnaps.snapd.Name, modelSnap.Name)
   143  			}
   144  			modelSnaps.snapd = modelSnap
   145  		case modelSnap.SnapType == "kernel":
   146  			essential = true
   147  			if modelSnaps.kernel != nil {
   148  				return nil, fmt.Errorf("cannot specify multiple kernel snaps: %q and %q", modelSnaps.kernel.Name, modelSnap.Name)
   149  			}
   150  			modelSnaps.kernel = modelSnap
   151  		case modelSnap.SnapType == "gadget":
   152  			essential = true
   153  			if modelSnaps.gadget != nil {
   154  				return nil, fmt.Errorf("cannot specify multiple gadget snaps: %q and %q", modelSnaps.gadget.Name, modelSnap.Name)
   155  			}
   156  			modelSnaps.gadget = modelSnap
   157  		case modelSnap.Name == base:
   158  			essential = true
   159  			if modelSnap.SnapType != "base" {
   160  				return nil, fmt.Errorf(`boot base %q must specify type "base", not %q`, base, modelSnap.SnapType)
   161  			}
   162  			modelSnaps.base = modelSnap
   163  		}
   164  
   165  		if essential {
   166  			if len(modelSnap.Modes) != 0 || modelSnap.Presence != "" {
   167  				return nil, fmt.Errorf("essential snaps are always available, cannot specify modes or presence for snap %q", modelSnap.Name)
   168  			}
   169  			modelSnap.Modes = essentialSnapModes
   170  		}
   171  
   172  		if len(modelSnap.Modes) == 0 {
   173  			modelSnap.Modes = defaultModes
   174  		}
   175  		if modelSnap.Presence == "" {
   176  			modelSnap.Presence = "required"
   177  		}
   178  
   179  		if !essential {
   180  			modelSnaps.snapsNoEssential = append(modelSnaps.snapsNoEssential, modelSnap)
   181  		}
   182  	}
   183  
   184  	return &modelSnaps, nil
   185  }
   186  
   187  var (
   188  	validSnapTypes     = []string{"app", "base", "gadget", "kernel", "core", "snapd"}
   189  	validSnapMode      = regexp.MustCompile("^[a-z][-a-z]+$")
   190  	validSnapPresences = []string{"required", "optional"}
   191  )
   192  
   193  func checkModelSnap(snap map[string]interface{}, grade ModelGrade) (*ModelSnap, error) {
   194  	name, err := checkNotEmptyStringWhat(snap, "name", "of snap")
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	if err := naming.ValidateSnap(name); err != nil {
   199  		return nil, fmt.Errorf("invalid snap name %q", name)
   200  	}
   201  
   202  	what := fmt.Sprintf("of snap %q", name)
   203  
   204  	var snapID string
   205  	_, ok := snap["id"]
   206  	if ok {
   207  		var err error
   208  		snapID, err = checkStringMatchesWhat(snap, "id", what, naming.ValidSnapID)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  	} else {
   213  		// snap ids are optional with grade dangerous to allow working
   214  		// with local/not pushed yet to the store snaps
   215  		if grade != ModelDangerous {
   216  			return nil, fmt.Errorf(`"id" %s is mandatory for %s grade model`, what, grade)
   217  		}
   218  	}
   219  
   220  	typ, err := checkOptionalStringWhat(snap, "type", what)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	if typ == "" {
   225  		typ = "app"
   226  	}
   227  	if !strutil.ListContains(validSnapTypes, typ) {
   228  		return nil, fmt.Errorf("type of snap %q must be one of %s", name, strings.Join(validSnapTypes, "|"))
   229  	}
   230  
   231  	modes, err := checkStringListInMap(snap, "modes", fmt.Sprintf("%q %s", "modes", what), validSnapMode)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	defaultChannel, err := checkOptionalStringWhat(snap, "default-channel", what)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	if defaultChannel == "" {
   241  		defaultChannel = "latest/stable"
   242  	}
   243  	defCh, err := channel.ParseVerbatim(defaultChannel, "-")
   244  	if err != nil {
   245  		return nil, fmt.Errorf("invalid default channel for snap %q: %v", name, err)
   246  	}
   247  	if defCh.Track == "" {
   248  		return nil, fmt.Errorf("default channel for snap %q must specify a track", name)
   249  	}
   250  
   251  	presence, err := checkOptionalStringWhat(snap, "presence", what)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	if presence != "" && !strutil.ListContains(validSnapPresences, presence) {
   256  		return nil, fmt.Errorf("presence of snap %q must be one of required|optional", name)
   257  	}
   258  
   259  	return &ModelSnap{
   260  		Name:           name,
   261  		SnapID:         snapID,
   262  		SnapType:       typ,
   263  		Modes:          modes, // can be empty
   264  		DefaultChannel: defaultChannel,
   265  		Presence:       presence, // can be empty
   266  	}, nil
   267  }
   268  
   269  // unextended case support
   270  
   271  func checkSnapWithTrack(headers map[string]interface{}, which string) (*ModelSnap, error) {
   272  	_, ok := headers[which]
   273  	if !ok {
   274  		return nil, nil
   275  	}
   276  	value, ok := headers[which].(string)
   277  	if !ok {
   278  		return nil, fmt.Errorf(`%q header must be a string`, which)
   279  	}
   280  	l := strings.SplitN(value, "=", 2)
   281  
   282  	name := l[0]
   283  	track := ""
   284  	if err := validateSnapName(name, which); err != nil {
   285  		return nil, err
   286  	}
   287  	if len(l) > 1 {
   288  		track = l[1]
   289  		if strings.Count(track, "/") != 0 {
   290  			return nil, fmt.Errorf(`%q channel selector must be a track name only`, which)
   291  		}
   292  		channelRisks := []string{"stable", "candidate", "beta", "edge"}
   293  		if strutil.ListContains(channelRisks, track) {
   294  			return nil, fmt.Errorf(`%q channel selector must be a track name`, which)
   295  		}
   296  	}
   297  
   298  	return &ModelSnap{
   299  		Name:        name,
   300  		SnapType:    which,
   301  		Modes:       defaultModes,
   302  		PinnedTrack: track,
   303  		Presence:    "required",
   304  	}, nil
   305  }
   306  
   307  func validateSnapName(name string, headerName string) error {
   308  	if err := naming.ValidateSnap(name); err != nil {
   309  		return fmt.Errorf("invalid snap name in %q header: %s", headerName, name)
   310  	}
   311  	return nil
   312  }
   313  
   314  func checkRequiredSnap(name string, headerName string, snapType string) (*ModelSnap, error) {
   315  	if err := validateSnapName(name, headerName); err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	return &ModelSnap{
   320  		Name:     name,
   321  		SnapType: snapType,
   322  		Modes:    defaultModes,
   323  		Presence: "required",
   324  	}, nil
   325  }
   326  
   327  // ModelGrade characterizes the security of the model which then
   328  // controls related policy.
   329  type ModelGrade string
   330  
   331  const (
   332  	ModelGradeUnset ModelGrade = "unset"
   333  	// ModelSecured implies mandatory full disk encryption and secure boot.
   334  	ModelSecured ModelGrade = "secured"
   335  	// ModelSigned implies all seed snaps are signed and mentioned
   336  	// in the model, i.e. no unasserted or extra snaps.
   337  	ModelSigned ModelGrade = "signed"
   338  	// ModelDangerous allows unasserted snaps and extra snaps.
   339  	ModelDangerous ModelGrade = "dangerous"
   340  )
   341  
   342  var validModelGrades = []string{string(ModelSecured), string(ModelSigned), string(ModelDangerous)}
   343  
   344  // gradeToCode encodes grades into 32 bits, trying to be slightly future-proof:
   345  // * lower 16 bits are reserved
   346  // * in the higher bits use the sequence 1, 8, 16 to have some space
   347  //   to possibly add new grades in between
   348  var gradeToCode = map[ModelGrade]uint32{
   349  	ModelGradeUnset: 0,
   350  	ModelDangerous:  0x10000,
   351  	ModelSigned:     0x80000,
   352  	ModelSecured:    0x100000,
   353  }
   354  
   355  // Code returns a bit representation of the grade, for example for
   356  // measuring it in a full disk encryption implementation.
   357  func (mg ModelGrade) Code() uint32 {
   358  	code, ok := gradeToCode[mg]
   359  	if !ok {
   360  		panic(fmt.Sprintf("unknown model grade: %s", mg))
   361  	}
   362  	return code
   363  }
   364  
   365  // Model holds a model assertion, which is a statement by a brand
   366  // about the properties of a device model.
   367  type Model struct {
   368  	assertionBase
   369  	classic bool
   370  
   371  	baseSnap   *ModelSnap
   372  	gadgetSnap *ModelSnap
   373  	kernelSnap *ModelSnap
   374  
   375  	grade ModelGrade
   376  
   377  	allSnaps []*ModelSnap
   378  	// consumers of this info should care only about snap identity =>
   379  	// snapRef
   380  	requiredWithEssentialSnaps []naming.SnapRef
   381  	numEssentialSnaps          int
   382  
   383  	serialAuthority  []string
   384  	sysUserAuthority []string
   385  	timestamp        time.Time
   386  }
   387  
   388  // BrandID returns the brand identifier. Same as the authority id.
   389  func (mod *Model) BrandID() string {
   390  	return mod.HeaderString("brand-id")
   391  }
   392  
   393  // Model returns the model name identifier.
   394  func (mod *Model) Model() string {
   395  	return mod.HeaderString("model")
   396  }
   397  
   398  // DisplayName returns the human-friendly name of the model or
   399  // falls back to Model if this was not set.
   400  func (mod *Model) DisplayName() string {
   401  	display := mod.HeaderString("display-name")
   402  	if display == "" {
   403  		return mod.Model()
   404  	}
   405  	return display
   406  }
   407  
   408  // Series returns the series of the core software the model uses.
   409  func (mod *Model) Series() string {
   410  	return mod.HeaderString("series")
   411  }
   412  
   413  // Classic returns whether the model is a classic system.
   414  func (mod *Model) Classic() bool {
   415  	return mod.classic
   416  }
   417  
   418  // Architecture returns the architecture the model is based on.
   419  func (mod *Model) Architecture() string {
   420  	return mod.HeaderString("architecture")
   421  }
   422  
   423  // Grade returns the stability grade of the model. Will be ModelGradeUnset
   424  // for Core 16/18 models.
   425  func (mod *Model) Grade() ModelGrade {
   426  	return mod.grade
   427  }
   428  
   429  // GadgetSnap returns the details of the gadget snap the model uses.
   430  func (mod *Model) GadgetSnap() *ModelSnap {
   431  	return mod.gadgetSnap
   432  }
   433  
   434  // Gadget returns the gadget snap the model uses.
   435  func (mod *Model) Gadget() string {
   436  	if mod.gadgetSnap == nil {
   437  		return ""
   438  	}
   439  	return mod.gadgetSnap.Name
   440  }
   441  
   442  // GadgetTrack returns the gadget track the model uses.
   443  // XXX this should go away
   444  func (mod *Model) GadgetTrack() string {
   445  	if mod.gadgetSnap == nil {
   446  		return ""
   447  	}
   448  	return mod.gadgetSnap.PinnedTrack
   449  }
   450  
   451  // KernelSnap returns the details of the kernel snap the model uses.
   452  func (mod *Model) KernelSnap() *ModelSnap {
   453  	return mod.kernelSnap
   454  }
   455  
   456  // Kernel returns the kernel snap the model uses.
   457  // XXX this should go away
   458  func (mod *Model) Kernel() string {
   459  	if mod.kernelSnap == nil {
   460  		return ""
   461  	}
   462  	return mod.kernelSnap.Name
   463  }
   464  
   465  // KernelTrack returns the kernel track the model uses.
   466  // XXX this should go away
   467  func (mod *Model) KernelTrack() string {
   468  	if mod.kernelSnap == nil {
   469  		return ""
   470  	}
   471  	return mod.kernelSnap.PinnedTrack
   472  }
   473  
   474  // Base returns the base snap the model uses.
   475  func (mod *Model) Base() string {
   476  	return mod.HeaderString("base")
   477  }
   478  
   479  // BaseSnap returns the details of the base snap the model uses.
   480  func (mod *Model) BaseSnap() *ModelSnap {
   481  	return mod.baseSnap
   482  }
   483  
   484  // Store returns the snap store the model uses.
   485  func (mod *Model) Store() string {
   486  	return mod.HeaderString("store")
   487  }
   488  
   489  // RequiredNoEssentialSnaps returns the snaps that must be installed at all times and cannot be removed for this model, excluding the essential snaps (gadget, kernel, boot base, snapd).
   490  func (mod *Model) RequiredNoEssentialSnaps() []naming.SnapRef {
   491  	return mod.requiredWithEssentialSnaps[mod.numEssentialSnaps:]
   492  }
   493  
   494  // RequiredWithEssentialSnaps returns the snaps that must be installed at all times and cannot be removed for this model, including any essential snaps (gadget, kernel, boot base, snapd).
   495  func (mod *Model) RequiredWithEssentialSnaps() []naming.SnapRef {
   496  	return mod.requiredWithEssentialSnaps
   497  }
   498  
   499  // EssentialSnaps returns all essential snaps explicitly mentioned by
   500  // the model.
   501  // They are always returned according to this order with some skipped
   502  // if not mentioned: snapd, kernel, boot base, gadget.
   503  func (mod *Model) EssentialSnaps() []*ModelSnap {
   504  	return mod.allSnaps[:mod.numEssentialSnaps]
   505  }
   506  
   507  // SnapsWithoutEssential returns all the snaps listed by the model
   508  // without any of the essential snaps (as returned by EssentialSnaps).
   509  // They are returned in the order of mention by the model.
   510  func (mod *Model) SnapsWithoutEssential() []*ModelSnap {
   511  	return mod.allSnaps[mod.numEssentialSnaps:]
   512  }
   513  
   514  // SerialAuthority returns the authority ids that are accepted as
   515  // signers for serial assertions for this model. It always includes the
   516  // brand of the model.
   517  func (mod *Model) SerialAuthority() []string {
   518  	return mod.serialAuthority
   519  }
   520  
   521  // SystemUserAuthority returns the authority ids that are accepted as
   522  // signers of system-user assertions for this model. Empty list means
   523  // any, otherwise it always includes the brand of the model.
   524  func (mod *Model) SystemUserAuthority() []string {
   525  	return mod.sysUserAuthority
   526  }
   527  
   528  // Timestamp returns the time when the model assertion was issued.
   529  func (mod *Model) Timestamp() time.Time {
   530  	return mod.timestamp
   531  }
   532  
   533  // Implement further consistency checks.
   534  func (mod *Model) checkConsistency(db RODatabase, acck *AccountKey) error {
   535  	// TODO: double check trust level of authority depending on class and possibly allowed-modes
   536  	return nil
   537  }
   538  
   539  // sanity
   540  var _ consistencyChecker = (*Model)(nil)
   541  
   542  // limit model to only lowercase for now
   543  var validModel = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
   544  
   545  func checkModel(headers map[string]interface{}) (string, error) {
   546  	s, err := checkStringMatches(headers, "model", validModel)
   547  	if err != nil {
   548  		return "", err
   549  	}
   550  
   551  	// TODO: support the concept of case insensitive/preserving string headers
   552  	if strings.ToLower(s) != s {
   553  		return "", fmt.Errorf(`"model" header cannot contain uppercase letters`)
   554  	}
   555  	return s, nil
   556  }
   557  
   558  func checkAuthorityMatchesBrand(a Assertion) error {
   559  	typeName := a.Type().Name
   560  	authorityID := a.AuthorityID()
   561  	brand := a.HeaderString("brand-id")
   562  	if brand != authorityID {
   563  		return fmt.Errorf("authority-id and brand-id must match, %s assertions are expected to be signed by the brand: %q != %q", typeName, authorityID, brand)
   564  	}
   565  	return nil
   566  }
   567  
   568  func checkOptionalSerialAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
   569  	ids := []string{brandID}
   570  	const name = "serial-authority"
   571  	if _, ok := headers[name]; !ok {
   572  		return ids, nil
   573  	}
   574  	if lst, err := checkStringListMatches(headers, name, validAccountID); err == nil {
   575  		if !strutil.ListContains(lst, brandID) {
   576  			lst = append(ids, lst...)
   577  		}
   578  		return lst, nil
   579  	}
   580  	return nil, fmt.Errorf("%q header must be a list of account ids", name)
   581  }
   582  
   583  func checkOptionalSystemUserAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
   584  	ids := []string{brandID}
   585  	const name = "system-user-authority"
   586  	v, ok := headers[name]
   587  	if !ok {
   588  		return ids, nil
   589  	}
   590  	switch x := v.(type) {
   591  	case string:
   592  		if x == "*" {
   593  			return nil, nil
   594  		}
   595  	case []interface{}:
   596  		lst, err := checkStringListMatches(headers, name, validAccountID)
   597  		if err == nil {
   598  			if !strutil.ListContains(lst, brandID) {
   599  				lst = append(ids, lst...)
   600  			}
   601  			return lst, nil
   602  		}
   603  	}
   604  	return nil, fmt.Errorf("%q header must be '*' or a list of account ids", name)
   605  }
   606  
   607  var (
   608  	modelMandatory           = []string{"architecture", "gadget", "kernel"}
   609  	extendedCoreMandatory    = []string{"architecture", "base"}
   610  	extendedSnapsConflicting = []string{"gadget", "kernel", "required-snaps"}
   611  	classicModelOptional     = []string{"architecture", "gadget"}
   612  )
   613  
   614  func assembleModel(assert assertionBase) (Assertion, error) {
   615  	err := checkAuthorityMatchesBrand(&assert)
   616  	if err != nil {
   617  		return nil, err
   618  	}
   619  
   620  	_, err = checkModel(assert.headers)
   621  	if err != nil {
   622  		return nil, err
   623  	}
   624  
   625  	classic, err := checkOptionalBool(assert.headers, "classic")
   626  	if err != nil {
   627  		return nil, err
   628  	}
   629  
   630  	// Core 20 extended snaps header
   631  	extendedSnaps, extended := assert.headers["snaps"]
   632  	if extended {
   633  		if classic {
   634  			return nil, fmt.Errorf("cannot use extended snaps header for a classic model (yet)")
   635  		}
   636  
   637  		for _, conflicting := range extendedSnapsConflicting {
   638  			if _, ok := assert.headers[conflicting]; ok {
   639  				return nil, fmt.Errorf("cannot specify separate %q header once using the extended snaps header", conflicting)
   640  			}
   641  		}
   642  	} else {
   643  		if _, ok := assert.headers["grade"]; ok {
   644  			return nil, fmt.Errorf("cannot specify a grade for model without the extended snaps header")
   645  		}
   646  	}
   647  
   648  	if classic {
   649  		if _, ok := assert.headers["kernel"]; ok {
   650  			return nil, fmt.Errorf("cannot specify a kernel with a classic model")
   651  		}
   652  		if _, ok := assert.headers["base"]; ok {
   653  			return nil, fmt.Errorf("cannot specify a base with a classic model")
   654  		}
   655  	}
   656  
   657  	checker := checkNotEmptyString
   658  	toCheck := modelMandatory
   659  	if extended {
   660  		toCheck = extendedCoreMandatory
   661  	} else if classic {
   662  		checker = checkOptionalString
   663  		toCheck = classicModelOptional
   664  	}
   665  
   666  	for _, h := range toCheck {
   667  		if _, err := checker(assert.headers, h); err != nil {
   668  			return nil, err
   669  		}
   670  	}
   671  
   672  	// base, if provided, must be a valid snap name too
   673  	var baseSnap *ModelSnap
   674  	base, err := checkOptionalString(assert.headers, "base")
   675  	if err != nil {
   676  		return nil, err
   677  	}
   678  	if base != "" {
   679  		baseSnap, err = checkRequiredSnap(base, "base", "base")
   680  		if err != nil {
   681  			return nil, err
   682  		}
   683  	}
   684  
   685  	// store is optional but must be a string, defaults to the ubuntu store
   686  	if _, err = checkOptionalString(assert.headers, "store"); err != nil {
   687  		return nil, err
   688  	}
   689  
   690  	// display-name is optional but must be a string
   691  	if _, err = checkOptionalString(assert.headers, "display-name"); err != nil {
   692  		return nil, err
   693  	}
   694  
   695  	var modSnaps *modelSnaps
   696  	grade := ModelGradeUnset
   697  	if extended {
   698  		gradeStr, err := checkOptionalString(assert.headers, "grade")
   699  		if err != nil {
   700  			return nil, err
   701  		}
   702  		if gradeStr != "" && !strutil.ListContains(validModelGrades, gradeStr) {
   703  			return nil, fmt.Errorf("grade for model must be %s, not %q", strings.Join(validModelGrades, "|"), gradeStr)
   704  		}
   705  		grade = ModelSigned
   706  		if gradeStr != "" {
   707  			grade = ModelGrade(gradeStr)
   708  		}
   709  
   710  		modSnaps, err = checkExtendedSnaps(extendedSnaps, base, grade)
   711  		if err != nil {
   712  			return nil, err
   713  		}
   714  		if modSnaps.gadget == nil {
   715  			return nil, fmt.Errorf(`one "snaps" header entry must specify the model gadget`)
   716  		}
   717  		if modSnaps.kernel == nil {
   718  			return nil, fmt.Errorf(`one "snaps" header entry must specify the model kernel`)
   719  		}
   720  
   721  		if modSnaps.base == nil {
   722  			// complete with defaults,
   723  			// the assumption is that base names are very stable
   724  			// essentially fixed
   725  			modSnaps.base = baseSnap
   726  			snapID := naming.WellKnownSnapID(modSnaps.base.Name)
   727  			if snapID == "" && grade != ModelDangerous {
   728  				return nil, fmt.Errorf(`cannot specify not well-known base %q without a corresponding "snaps" header entry`, modSnaps.base.Name)
   729  			}
   730  			modSnaps.base.SnapID = snapID
   731  			modSnaps.base.Modes = essentialSnapModes
   732  			modSnaps.base.DefaultChannel = "latest/stable"
   733  		}
   734  	} else {
   735  		modSnaps = &modelSnaps{
   736  			base: baseSnap,
   737  		}
   738  		// kernel/gadget must be valid snap names and can have (optional) tracks
   739  		// - validate those
   740  		modSnaps.kernel, err = checkSnapWithTrack(assert.headers, "kernel")
   741  		if err != nil {
   742  			return nil, err
   743  		}
   744  		modSnaps.gadget, err = checkSnapWithTrack(assert.headers, "gadget")
   745  		if err != nil {
   746  			return nil, err
   747  		}
   748  
   749  		// required snap must be valid snap names
   750  		reqSnaps, err := checkStringList(assert.headers, "required-snaps")
   751  		if err != nil {
   752  			return nil, err
   753  		}
   754  		for _, name := range reqSnaps {
   755  			reqSnap, err := checkRequiredSnap(name, "required-snaps", "")
   756  			if err != nil {
   757  				return nil, err
   758  			}
   759  			modSnaps.snapsNoEssential = append(modSnaps.snapsNoEssential, reqSnap)
   760  		}
   761  	}
   762  
   763  	brandID := assert.HeaderString("brand-id")
   764  
   765  	serialAuthority, err := checkOptionalSerialAuthority(assert.headers, brandID)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  
   770  	sysUserAuthority, err := checkOptionalSystemUserAuthority(assert.headers, brandID)
   771  	if err != nil {
   772  		return nil, err
   773  	}
   774  
   775  	timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
   776  	if err != nil {
   777  		return nil, err
   778  	}
   779  
   780  	allSnaps, requiredWithEssentialSnaps, numEssentialSnaps := modSnaps.list()
   781  
   782  	// NB:
   783  	// * core is not supported at this time, it defaults to ubuntu-core
   784  	// in prepare-image until rename and/or introduction of the header.
   785  	// * some form of allowed-modes, class are postponed,
   786  	//
   787  	// prepare-image takes care of not allowing them for now
   788  
   789  	// ignore extra headers and non-empty body for future compatibility
   790  	return &Model{
   791  		assertionBase:              assert,
   792  		classic:                    classic,
   793  		baseSnap:                   modSnaps.base,
   794  		gadgetSnap:                 modSnaps.gadget,
   795  		kernelSnap:                 modSnaps.kernel,
   796  		grade:                      grade,
   797  		allSnaps:                   allSnaps,
   798  		requiredWithEssentialSnaps: requiredWithEssentialSnaps,
   799  		numEssentialSnaps:          numEssentialSnaps,
   800  		serialAuthority:            serialAuthority,
   801  		sysUserAuthority:           sysUserAuthority,
   802  		timestamp:                  timestamp,
   803  	}, nil
   804  }