github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  // StorageSafety characterizes the requested storage safety of
   343  // the model which then controls what encryption is used
   344  type StorageSafety string
   345  
   346  const (
   347  	StorageSafetyUnset StorageSafety = "unset"
   348  	// StorageSafetyEncrypted implies mandatory full disk encryption.
   349  	StorageSafetyEncrypted StorageSafety = "encrypted"
   350  	// StorageSafetyPreferEncrypted implies full disk
   351  	// encryption when the system supports it.
   352  	StorageSafetyPreferEncrypted StorageSafety = "prefer-encrypted"
   353  	// StorageSafetyPreferUnencrypted implies no full disk
   354  	// encryption by default even if the system supports
   355  	// encryption.
   356  	StorageSafetyPreferUnencrypted StorageSafety = "prefer-unencrypted"
   357  )
   358  
   359  var validStorageSafeties = []string{string(StorageSafetyEncrypted), string(StorageSafetyPreferEncrypted), string(StorageSafetyPreferUnencrypted)}
   360  
   361  var validModelGrades = []string{string(ModelSecured), string(ModelSigned), string(ModelDangerous)}
   362  
   363  // gradeToCode encodes grades into 32 bits, trying to be slightly future-proof:
   364  // * lower 16 bits are reserved
   365  // * in the higher bits use the sequence 1, 8, 16 to have some space
   366  //   to possibly add new grades in between
   367  var gradeToCode = map[ModelGrade]uint32{
   368  	ModelGradeUnset: 0,
   369  	ModelDangerous:  0x10000,
   370  	ModelSigned:     0x80000,
   371  	ModelSecured:    0x100000,
   372  }
   373  
   374  // Code returns a bit representation of the grade, for example for
   375  // measuring it in a full disk encryption implementation.
   376  func (mg ModelGrade) Code() uint32 {
   377  	code, ok := gradeToCode[mg]
   378  	if !ok {
   379  		panic(fmt.Sprintf("unknown model grade: %s", mg))
   380  	}
   381  	return code
   382  }
   383  
   384  // Model holds a model assertion, which is a statement by a brand
   385  // about the properties of a device model.
   386  type Model struct {
   387  	assertionBase
   388  	classic bool
   389  
   390  	baseSnap   *ModelSnap
   391  	gadgetSnap *ModelSnap
   392  	kernelSnap *ModelSnap
   393  
   394  	grade ModelGrade
   395  
   396  	storageSafety StorageSafety
   397  
   398  	allSnaps []*ModelSnap
   399  	// consumers of this info should care only about snap identity =>
   400  	// snapRef
   401  	requiredWithEssentialSnaps []naming.SnapRef
   402  	numEssentialSnaps          int
   403  
   404  	serialAuthority  []string
   405  	sysUserAuthority []string
   406  	timestamp        time.Time
   407  }
   408  
   409  // BrandID returns the brand identifier. Same as the authority id.
   410  func (mod *Model) BrandID() string {
   411  	return mod.HeaderString("brand-id")
   412  }
   413  
   414  // Model returns the model name identifier.
   415  func (mod *Model) Model() string {
   416  	return mod.HeaderString("model")
   417  }
   418  
   419  // DisplayName returns the human-friendly name of the model or
   420  // falls back to Model if this was not set.
   421  func (mod *Model) DisplayName() string {
   422  	display := mod.HeaderString("display-name")
   423  	if display == "" {
   424  		return mod.Model()
   425  	}
   426  	return display
   427  }
   428  
   429  // Series returns the series of the core software the model uses.
   430  func (mod *Model) Series() string {
   431  	return mod.HeaderString("series")
   432  }
   433  
   434  // Classic returns whether the model is a classic system.
   435  func (mod *Model) Classic() bool {
   436  	return mod.classic
   437  }
   438  
   439  // Architecture returns the architecture the model is based on.
   440  func (mod *Model) Architecture() string {
   441  	return mod.HeaderString("architecture")
   442  }
   443  
   444  // Grade returns the stability grade of the model. Will be ModelGradeUnset
   445  // for Core 16/18 models.
   446  func (mod *Model) Grade() ModelGrade {
   447  	return mod.grade
   448  }
   449  
   450  // StorageSafety returns the storage safety for the model. Will be
   451  // StorageSafetyUnset for Core 16/18 models.
   452  func (mod *Model) StorageSafety() StorageSafety {
   453  	return mod.storageSafety
   454  }
   455  
   456  // GadgetSnap returns the details of the gadget snap the model uses.
   457  func (mod *Model) GadgetSnap() *ModelSnap {
   458  	return mod.gadgetSnap
   459  }
   460  
   461  // Gadget returns the gadget snap the model uses.
   462  func (mod *Model) Gadget() string {
   463  	if mod.gadgetSnap == nil {
   464  		return ""
   465  	}
   466  	return mod.gadgetSnap.Name
   467  }
   468  
   469  // GadgetTrack returns the gadget track the model uses.
   470  // XXX this should go away
   471  func (mod *Model) GadgetTrack() string {
   472  	if mod.gadgetSnap == nil {
   473  		return ""
   474  	}
   475  	return mod.gadgetSnap.PinnedTrack
   476  }
   477  
   478  // KernelSnap returns the details of the kernel snap the model uses.
   479  func (mod *Model) KernelSnap() *ModelSnap {
   480  	return mod.kernelSnap
   481  }
   482  
   483  // Kernel returns the kernel snap the model uses.
   484  // XXX this should go away
   485  func (mod *Model) Kernel() string {
   486  	if mod.kernelSnap == nil {
   487  		return ""
   488  	}
   489  	return mod.kernelSnap.Name
   490  }
   491  
   492  // KernelTrack returns the kernel track the model uses.
   493  // XXX this should go away
   494  func (mod *Model) KernelTrack() string {
   495  	if mod.kernelSnap == nil {
   496  		return ""
   497  	}
   498  	return mod.kernelSnap.PinnedTrack
   499  }
   500  
   501  // Base returns the base snap the model uses.
   502  func (mod *Model) Base() string {
   503  	return mod.HeaderString("base")
   504  }
   505  
   506  // BaseSnap returns the details of the base snap the model uses.
   507  func (mod *Model) BaseSnap() *ModelSnap {
   508  	return mod.baseSnap
   509  }
   510  
   511  // Store returns the snap store the model uses.
   512  func (mod *Model) Store() string {
   513  	return mod.HeaderString("store")
   514  }
   515  
   516  // 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).
   517  func (mod *Model) RequiredNoEssentialSnaps() []naming.SnapRef {
   518  	return mod.requiredWithEssentialSnaps[mod.numEssentialSnaps:]
   519  }
   520  
   521  // 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).
   522  func (mod *Model) RequiredWithEssentialSnaps() []naming.SnapRef {
   523  	return mod.requiredWithEssentialSnaps
   524  }
   525  
   526  // EssentialSnaps returns all essential snaps explicitly mentioned by
   527  // the model.
   528  // They are always returned according to this order with some skipped
   529  // if not mentioned: snapd, kernel, boot base, gadget.
   530  func (mod *Model) EssentialSnaps() []*ModelSnap {
   531  	return mod.allSnaps[:mod.numEssentialSnaps]
   532  }
   533  
   534  // SnapsWithoutEssential returns all the snaps listed by the model
   535  // without any of the essential snaps (as returned by EssentialSnaps).
   536  // They are returned in the order of mention by the model.
   537  func (mod *Model) SnapsWithoutEssential() []*ModelSnap {
   538  	return mod.allSnaps[mod.numEssentialSnaps:]
   539  }
   540  
   541  // SerialAuthority returns the authority ids that are accepted as
   542  // signers for serial assertions for this model. It always includes the
   543  // brand of the model.
   544  func (mod *Model) SerialAuthority() []string {
   545  	return mod.serialAuthority
   546  }
   547  
   548  // SystemUserAuthority returns the authority ids that are accepted as
   549  // signers of system-user assertions for this model. Empty list means
   550  // any, otherwise it always includes the brand of the model.
   551  func (mod *Model) SystemUserAuthority() []string {
   552  	return mod.sysUserAuthority
   553  }
   554  
   555  // Timestamp returns the time when the model assertion was issued.
   556  func (mod *Model) Timestamp() time.Time {
   557  	return mod.timestamp
   558  }
   559  
   560  // Implement further consistency checks.
   561  func (mod *Model) checkConsistency(db RODatabase, acck *AccountKey) error {
   562  	// TODO: double check trust level of authority depending on class and possibly allowed-modes
   563  	return nil
   564  }
   565  
   566  // sanity
   567  var _ consistencyChecker = (*Model)(nil)
   568  
   569  // limit model to only lowercase for now
   570  var validModel = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
   571  
   572  func checkModel(headers map[string]interface{}) (string, error) {
   573  	s, err := checkStringMatches(headers, "model", validModel)
   574  	if err != nil {
   575  		return "", err
   576  	}
   577  
   578  	// TODO: support the concept of case insensitive/preserving string headers
   579  	if strings.ToLower(s) != s {
   580  		return "", fmt.Errorf(`"model" header cannot contain uppercase letters`)
   581  	}
   582  	return s, nil
   583  }
   584  
   585  func checkAuthorityMatchesBrand(a Assertion) error {
   586  	typeName := a.Type().Name
   587  	authorityID := a.AuthorityID()
   588  	brand := a.HeaderString("brand-id")
   589  	if brand != authorityID {
   590  		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)
   591  	}
   592  	return nil
   593  }
   594  
   595  func checkOptionalSerialAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
   596  	ids := []string{brandID}
   597  	const name = "serial-authority"
   598  	if _, ok := headers[name]; !ok {
   599  		return ids, nil
   600  	}
   601  	if lst, err := checkStringListMatches(headers, name, validAccountID); err == nil {
   602  		if !strutil.ListContains(lst, brandID) {
   603  			lst = append(ids, lst...)
   604  		}
   605  		return lst, nil
   606  	}
   607  	return nil, fmt.Errorf("%q header must be a list of account ids", name)
   608  }
   609  
   610  func checkOptionalSystemUserAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
   611  	ids := []string{brandID}
   612  	const name = "system-user-authority"
   613  	v, ok := headers[name]
   614  	if !ok {
   615  		return ids, nil
   616  	}
   617  	switch x := v.(type) {
   618  	case string:
   619  		if x == "*" {
   620  			return nil, nil
   621  		}
   622  	case []interface{}:
   623  		lst, err := checkStringListMatches(headers, name, validAccountID)
   624  		if err == nil {
   625  			if !strutil.ListContains(lst, brandID) {
   626  				lst = append(ids, lst...)
   627  			}
   628  			return lst, nil
   629  		}
   630  	}
   631  	return nil, fmt.Errorf("%q header must be '*' or a list of account ids", name)
   632  }
   633  
   634  var (
   635  	modelMandatory           = []string{"architecture", "gadget", "kernel"}
   636  	extendedCoreMandatory    = []string{"architecture", "base"}
   637  	extendedSnapsConflicting = []string{"gadget", "kernel", "required-snaps"}
   638  	classicModelOptional     = []string{"architecture", "gadget"}
   639  )
   640  
   641  func assembleModel(assert assertionBase) (Assertion, error) {
   642  	err := checkAuthorityMatchesBrand(&assert)
   643  	if err != nil {
   644  		return nil, err
   645  	}
   646  
   647  	_, err = checkModel(assert.headers)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  
   652  	classic, err := checkOptionalBool(assert.headers, "classic")
   653  	if err != nil {
   654  		return nil, err
   655  	}
   656  
   657  	// Core 20 extended snaps header
   658  	extendedSnaps, extended := assert.headers["snaps"]
   659  	if extended {
   660  		if classic {
   661  			return nil, fmt.Errorf("cannot use extended snaps header for a classic model (yet)")
   662  		}
   663  
   664  		for _, conflicting := range extendedSnapsConflicting {
   665  			if _, ok := assert.headers[conflicting]; ok {
   666  				return nil, fmt.Errorf("cannot specify separate %q header once using the extended snaps header", conflicting)
   667  			}
   668  		}
   669  	} else {
   670  		if _, ok := assert.headers["grade"]; ok {
   671  			return nil, fmt.Errorf("cannot specify a grade for model without the extended snaps header")
   672  		}
   673  		if _, ok := assert.headers["storage-safety"]; ok {
   674  			return nil, fmt.Errorf("cannot specify storage-safety for model without the extended snaps header")
   675  		}
   676  	}
   677  
   678  	if classic {
   679  		if _, ok := assert.headers["kernel"]; ok {
   680  			return nil, fmt.Errorf("cannot specify a kernel with a classic model")
   681  		}
   682  		if _, ok := assert.headers["base"]; ok {
   683  			return nil, fmt.Errorf("cannot specify a base with a classic model")
   684  		}
   685  	}
   686  
   687  	checker := checkNotEmptyString
   688  	toCheck := modelMandatory
   689  	if extended {
   690  		toCheck = extendedCoreMandatory
   691  	} else if classic {
   692  		checker = checkOptionalString
   693  		toCheck = classicModelOptional
   694  	}
   695  
   696  	for _, h := range toCheck {
   697  		if _, err := checker(assert.headers, h); err != nil {
   698  			return nil, err
   699  		}
   700  	}
   701  
   702  	// base, if provided, must be a valid snap name too
   703  	var baseSnap *ModelSnap
   704  	base, err := checkOptionalString(assert.headers, "base")
   705  	if err != nil {
   706  		return nil, err
   707  	}
   708  	if base != "" {
   709  		baseSnap, err = checkRequiredSnap(base, "base", "base")
   710  		if err != nil {
   711  			return nil, err
   712  		}
   713  	}
   714  
   715  	// store is optional but must be a string, defaults to the ubuntu store
   716  	if _, err = checkOptionalString(assert.headers, "store"); err != nil {
   717  		return nil, err
   718  	}
   719  
   720  	// display-name is optional but must be a string
   721  	if _, err = checkOptionalString(assert.headers, "display-name"); err != nil {
   722  		return nil, err
   723  	}
   724  
   725  	var modSnaps *modelSnaps
   726  	grade := ModelGradeUnset
   727  	storageSafety := StorageSafetyUnset
   728  	if extended {
   729  		gradeStr, err := checkOptionalString(assert.headers, "grade")
   730  		if err != nil {
   731  			return nil, err
   732  		}
   733  		if gradeStr != "" && !strutil.ListContains(validModelGrades, gradeStr) {
   734  			return nil, fmt.Errorf("grade for model must be %s, not %q", strings.Join(validModelGrades, "|"), gradeStr)
   735  		}
   736  		grade = ModelSigned
   737  		if gradeStr != "" {
   738  			grade = ModelGrade(gradeStr)
   739  		}
   740  
   741  		storageSafetyStr, err := checkOptionalString(assert.headers, "storage-safety")
   742  		if err != nil {
   743  			return nil, err
   744  		}
   745  		if storageSafetyStr != "" && !strutil.ListContains(validStorageSafeties, storageSafetyStr) {
   746  			return nil, fmt.Errorf("storage-safety for model must be %s, not %q", strings.Join(validStorageSafeties, "|"), storageSafetyStr)
   747  		}
   748  		if storageSafetyStr != "" {
   749  			storageSafety = StorageSafety(storageSafetyStr)
   750  		} else {
   751  			if grade == ModelSecured {
   752  				storageSafety = StorageSafetyEncrypted
   753  			} else {
   754  				storageSafety = StorageSafetyPreferEncrypted
   755  			}
   756  		}
   757  
   758  		if grade == ModelSecured && storageSafety != StorageSafetyEncrypted {
   759  			return nil, fmt.Errorf(`secured grade model must not have storage-safety overridden, only "encrypted" is valid`)
   760  		}
   761  
   762  		modSnaps, err = checkExtendedSnaps(extendedSnaps, base, grade)
   763  		if err != nil {
   764  			return nil, err
   765  		}
   766  		if modSnaps.gadget == nil {
   767  			return nil, fmt.Errorf(`one "snaps" header entry must specify the model gadget`)
   768  		}
   769  		if modSnaps.kernel == nil {
   770  			return nil, fmt.Errorf(`one "snaps" header entry must specify the model kernel`)
   771  		}
   772  
   773  		if modSnaps.base == nil {
   774  			// complete with defaults,
   775  			// the assumption is that base names are very stable
   776  			// essentially fixed
   777  			modSnaps.base = baseSnap
   778  			snapID := naming.WellKnownSnapID(modSnaps.base.Name)
   779  			if snapID == "" && grade != ModelDangerous {
   780  				return nil, fmt.Errorf(`cannot specify not well-known base %q without a corresponding "snaps" header entry`, modSnaps.base.Name)
   781  			}
   782  			modSnaps.base.SnapID = snapID
   783  			modSnaps.base.Modes = essentialSnapModes
   784  			modSnaps.base.DefaultChannel = "latest/stable"
   785  		}
   786  	} else {
   787  		modSnaps = &modelSnaps{
   788  			base: baseSnap,
   789  		}
   790  		// kernel/gadget must be valid snap names and can have (optional) tracks
   791  		// - validate those
   792  		modSnaps.kernel, err = checkSnapWithTrack(assert.headers, "kernel")
   793  		if err != nil {
   794  			return nil, err
   795  		}
   796  		modSnaps.gadget, err = checkSnapWithTrack(assert.headers, "gadget")
   797  		if err != nil {
   798  			return nil, err
   799  		}
   800  
   801  		// required snap must be valid snap names
   802  		reqSnaps, err := checkStringList(assert.headers, "required-snaps")
   803  		if err != nil {
   804  			return nil, err
   805  		}
   806  		for _, name := range reqSnaps {
   807  			reqSnap, err := checkRequiredSnap(name, "required-snaps", "")
   808  			if err != nil {
   809  				return nil, err
   810  			}
   811  			modSnaps.snapsNoEssential = append(modSnaps.snapsNoEssential, reqSnap)
   812  		}
   813  	}
   814  
   815  	brandID := assert.HeaderString("brand-id")
   816  
   817  	serialAuthority, err := checkOptionalSerialAuthority(assert.headers, brandID)
   818  	if err != nil {
   819  		return nil, err
   820  	}
   821  
   822  	sysUserAuthority, err := checkOptionalSystemUserAuthority(assert.headers, brandID)
   823  	if err != nil {
   824  		return nil, err
   825  	}
   826  
   827  	timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
   828  	if err != nil {
   829  		return nil, err
   830  	}
   831  
   832  	allSnaps, requiredWithEssentialSnaps, numEssentialSnaps := modSnaps.list()
   833  
   834  	// NB:
   835  	// * core is not supported at this time, it defaults to ubuntu-core
   836  	// in prepare-image until rename and/or introduction of the header.
   837  	// * some form of allowed-modes, class are postponed,
   838  	//
   839  	// prepare-image takes care of not allowing them for now
   840  
   841  	// ignore extra headers and non-empty body for future compatibility
   842  	return &Model{
   843  		assertionBase:              assert,
   844  		classic:                    classic,
   845  		baseSnap:                   modSnaps.base,
   846  		gadgetSnap:                 modSnaps.gadget,
   847  		kernelSnap:                 modSnaps.kernel,
   848  		grade:                      grade,
   849  		storageSafety:              storageSafety,
   850  		allSnaps:                   allSnaps,
   851  		requiredWithEssentialSnaps: requiredWithEssentialSnaps,
   852  		numEssentialSnaps:          numEssentialSnaps,
   853  		serialAuthority:            serialAuthority,
   854  		sysUserAuthority:           sysUserAuthority,
   855  		timestamp:                  timestamp,
   856  	}, nil
   857  }