gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/model.go (about)

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