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 }