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 }