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 }