github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/model_test.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_test 21 22 import ( 23 "fmt" 24 "strings" 25 "time" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/snap/naming" 31 ) 32 33 type modelSuite struct { 34 ts time.Time 35 tsLine string 36 } 37 38 var ( 39 _ = Suite(&modelSuite{}) 40 ) 41 42 func (mods *modelSuite) SetUpSuite(c *C) { 43 mods.ts = time.Now().Truncate(time.Second).UTC() 44 mods.tsLine = "timestamp: " + mods.ts.Format(time.RFC3339) + "\n" 45 } 46 47 const ( 48 reqSnaps = "required-snaps:\n - foo\n - bar\n" 49 sysUserAuths = "system-user-authority: *\n" 50 serialAuths = "serial-authority:\n - generic\n" 51 ) 52 53 const ( 54 modelExample = "type: model\n" + 55 "authority-id: brand-id1\n" + 56 "series: 16\n" + 57 "brand-id: brand-id1\n" + 58 "model: baz-3000\n" + 59 "display-name: Baz 3000\n" + 60 "architecture: amd64\n" + 61 "gadget: brand-gadget\n" + 62 "base: core18\n" + 63 "kernel: baz-linux\n" + 64 "store: brand-store\n" + 65 serialAuths + 66 sysUserAuths + 67 reqSnaps + 68 "TSLINE" + 69 "body-length: 0\n" + 70 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + 71 "\n\n" + 72 "AXNpZw==" 73 74 classicModelExample = "type: model\n" + 75 "authority-id: brand-id1\n" + 76 "series: 16\n" + 77 "brand-id: brand-id1\n" + 78 "model: baz-3000\n" + 79 "display-name: Baz 3000\n" + 80 "classic: true\n" + 81 "architecture: amd64\n" + 82 "gadget: brand-gadget\n" + 83 "store: brand-store\n" + 84 reqSnaps + 85 "TSLINE" + 86 "body-length: 0\n" + 87 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + 88 "\n\n" + 89 "AXNpZw==" 90 91 core20ModelExample = `type: model 92 authority-id: brand-id1 93 series: 16 94 brand-id: brand-id1 95 model: baz-3000 96 display-name: Baz 3000 97 architecture: amd64 98 system-user-authority: * 99 base: core20 100 store: brand-store 101 snaps: 102 - 103 name: baz-linux 104 id: bazlinuxidididididididididididid 105 type: kernel 106 default-channel: 20 107 - 108 name: brand-gadget 109 id: brandgadgetdidididididididididid 110 type: gadget 111 - 112 name: other-base 113 id: otherbasedididididididididididid 114 type: base 115 modes: 116 - run 117 presence: required 118 - 119 name: nm 120 id: nmididididididididididididididid 121 modes: 122 - ephemeral 123 - run 124 default-channel: 1.0 125 - 126 name: myapp 127 id: myappdididididididididididididid 128 type: app 129 default-channel: 2.0 130 - 131 name: myappopt 132 id: myappoptidididididididididididid 133 type: app 134 presence: optional 135 OTHERgrade: secured 136 storage-safety: encrypted 137 ` + "TSLINE" + 138 "body-length: 0\n" + 139 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + 140 "\n\n" + 141 "AXNpZw==" 142 ) 143 144 func (mods *modelSuite) TestDecodeOK(c *C) { 145 encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 146 a, err := asserts.Decode([]byte(encoded)) 147 c.Assert(err, IsNil) 148 c.Check(a.Type(), Equals, asserts.ModelType) 149 model := a.(*asserts.Model) 150 c.Check(model.AuthorityID(), Equals, "brand-id1") 151 c.Check(model.Timestamp(), Equals, mods.ts) 152 c.Check(model.Series(), Equals, "16") 153 c.Check(model.BrandID(), Equals, "brand-id1") 154 c.Check(model.Model(), Equals, "baz-3000") 155 c.Check(model.DisplayName(), Equals, "Baz 3000") 156 c.Check(model.Architecture(), Equals, "amd64") 157 c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{ 158 Name: "brand-gadget", 159 SnapType: "gadget", 160 Modes: []string{"run"}, 161 Presence: "required", 162 }) 163 c.Check(model.Gadget(), Equals, "brand-gadget") 164 c.Check(model.GadgetTrack(), Equals, "") 165 c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{ 166 Name: "baz-linux", 167 SnapType: "kernel", 168 Modes: []string{"run"}, 169 Presence: "required", 170 }) 171 c.Check(model.Kernel(), Equals, "baz-linux") 172 c.Check(model.KernelTrack(), Equals, "") 173 c.Check(model.Base(), Equals, "core18") 174 c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{ 175 Name: "core18", 176 SnapType: "base", 177 Modes: []string{"run"}, 178 Presence: "required", 179 }) 180 c.Check(model.Store(), Equals, "brand-store") 181 c.Check(model.Grade(), Equals, asserts.ModelGradeUnset) 182 c.Check(model.StorageSafety(), Equals, asserts.StorageSafetyUnset) 183 essentialSnaps := model.EssentialSnaps() 184 c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{ 185 model.KernelSnap(), 186 model.BaseSnap(), 187 model.GadgetSnap(), 188 }) 189 snaps := model.SnapsWithoutEssential() 190 c.Check(snaps, DeepEquals, []*asserts.ModelSnap{ 191 { 192 Name: "foo", 193 Modes: []string{"run"}, 194 Presence: "required", 195 }, 196 { 197 Name: "bar", 198 Modes: []string{"run"}, 199 Presence: "required", 200 }, 201 }) 202 // essential snaps included 203 reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps()) 204 for _, e := range essentialSnaps { 205 c.Check(reqSnaps.Contains(e), Equals, true) 206 } 207 for _, s := range snaps { 208 c.Check(reqSnaps.Contains(s), Equals, true) 209 } 210 c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps)) 211 // essential snaps excluded 212 noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps()) 213 for _, e := range essentialSnaps { 214 c.Check(noEssential.Contains(e), Equals, false) 215 } 216 for _, s := range snaps { 217 c.Check(noEssential.Contains(s), Equals, true) 218 } 219 c.Check(noEssential.Size(), Equals, len(snaps)) 220 221 c.Check(model.SystemUserAuthority(), HasLen, 0) 222 c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1", "generic"}) 223 } 224 225 func (mods *modelSuite) TestDecodeStoreIsOptional(c *C) { 226 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 227 encoded := strings.Replace(withTimestamp, "store: brand-store\n", "store: \n", 1) 228 a, err := asserts.Decode([]byte(encoded)) 229 c.Assert(err, IsNil) 230 model := a.(*asserts.Model) 231 c.Check(model.Store(), Equals, "") 232 233 encoded = strings.Replace(withTimestamp, "store: brand-store\n", "", 1) 234 a, err = asserts.Decode([]byte(encoded)) 235 c.Assert(err, IsNil) 236 model = a.(*asserts.Model) 237 c.Check(model.Store(), Equals, "") 238 } 239 240 func (mods *modelSuite) TestDecodeBaseIsOptional(c *C) { 241 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 242 encoded := strings.Replace(withTimestamp, "base: core18\n", "base: \n", 1) 243 a, err := asserts.Decode([]byte(encoded)) 244 c.Assert(err, IsNil) 245 model := a.(*asserts.Model) 246 c.Check(model.Base(), Equals, "") 247 248 encoded = strings.Replace(withTimestamp, "base: core18\n", "", 1) 249 a, err = asserts.Decode([]byte(encoded)) 250 c.Assert(err, IsNil) 251 model = a.(*asserts.Model) 252 c.Check(model.Base(), Equals, "") 253 c.Check(model.BaseSnap(), IsNil) 254 } 255 256 func (mods *modelSuite) TestDecodeDisplayNameIsOptional(c *C) { 257 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 258 encoded := strings.Replace(withTimestamp, "display-name: Baz 3000\n", "display-name: \n", 1) 259 a, err := asserts.Decode([]byte(encoded)) 260 c.Assert(err, IsNil) 261 model := a.(*asserts.Model) 262 // optional but we fallback to Model 263 c.Check(model.DisplayName(), Equals, "baz-3000") 264 265 encoded = strings.Replace(withTimestamp, "display-name: Baz 3000\n", "", 1) 266 a, err = asserts.Decode([]byte(encoded)) 267 c.Assert(err, IsNil) 268 model = a.(*asserts.Model) 269 // optional but we fallback to Model 270 c.Check(model.DisplayName(), Equals, "baz-3000") 271 } 272 273 func (mods *modelSuite) TestDecodeRequiredSnapsAreOptional(c *C) { 274 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 275 encoded := strings.Replace(withTimestamp, reqSnaps, "", 1) 276 a, err := asserts.Decode([]byte(encoded)) 277 c.Assert(err, IsNil) 278 model := a.(*asserts.Model) 279 c.Check(model.RequiredNoEssentialSnaps(), HasLen, 0) 280 } 281 282 func (mods *modelSuite) TestDecodeValidatesSnapNames(c *C) { 283 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 284 encoded := strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo_bar\n - bar\n", 1) 285 a, err := asserts.Decode([]byte(encoded)) 286 c.Assert(a, IsNil) 287 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: foo_bar`) 288 289 encoded = strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo\n - bar-;;''\n", 1) 290 a, err = asserts.Decode([]byte(encoded)) 291 c.Assert(a, IsNil) 292 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: bar-;;''`) 293 294 encoded = strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux_instance\n", 1) 295 a, err = asserts.Decode([]byte(encoded)) 296 c.Assert(a, IsNil) 297 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: baz-linux_instance`) 298 299 encoded = strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget_instance\n", 1) 300 a, err = asserts.Decode([]byte(encoded)) 301 c.Assert(a, IsNil) 302 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "gadget" header: brand-gadget_instance`) 303 304 encoded = strings.Replace(withTimestamp, "base: core18\n", "base: core18_instance\n", 1) 305 a, err = asserts.Decode([]byte(encoded)) 306 c.Assert(a, IsNil) 307 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "base" header: core18_instance`) 308 } 309 310 func (mods modelSuite) TestDecodeValidSnapNames(c *C) { 311 // reuse test cases for snap.ValidateName() 312 313 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 314 315 validNames := []string{ 316 "aa", "aaa", "aaaa", 317 "a-a", "aa-a", "a-aa", "a-b-c", 318 "a0", "a-0", "a-0a", 319 "01game", "1-or-2", 320 // a regexp stresser 321 "u-94903713687486543234157734673284536758", 322 } 323 for _, name := range validNames { 324 encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1) 325 a, err := asserts.Decode([]byte(encoded)) 326 c.Assert(err, IsNil) 327 model := a.(*asserts.Model) 328 c.Check(model.Kernel(), Equals, name) 329 } 330 invalidNames := []string{ 331 // name cannot be empty, never reaches snap name validation 332 "", 333 // too short (min 2 chars) 334 "a", 335 // names cannot be too long 336 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 337 "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx", 338 "1111111111111111111111111111111111111111x", 339 "x1111111111111111111111111111111111111111", 340 "x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x", 341 // a regexp stresser 342 "u-9490371368748654323415773467328453675-", 343 // dashes alone are not a name 344 "-", "--", 345 // double dashes in a name are not allowed 346 "a--a", 347 // name should not end with a dash 348 "a-", 349 // name cannot have any spaces in it 350 "a ", " a", "a a", 351 // a number alone is not a name 352 "0", "123", 353 // identifier must be plain ASCII 354 "日本語", "한글", "ру́сский язы́к", 355 // instance names are invalid too 356 "foo_bar", "x_1", 357 } 358 for _, name := range invalidNames { 359 encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1) 360 a, err := asserts.Decode([]byte(encoded)) 361 c.Assert(a, IsNil) 362 if name != "" { 363 c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: .*`) 364 } else { 365 c.Assert(err, ErrorMatches, `assertion model: "kernel" header should not be empty`) 366 } 367 } 368 } 369 370 func (mods *modelSuite) TestDecodeSerialAuthorityIsOptional(c *C) { 371 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 372 encoded := strings.Replace(withTimestamp, serialAuths, "", 1) 373 a, err := asserts.Decode([]byte(encoded)) 374 c.Assert(err, IsNil) 375 model := a.(*asserts.Model) 376 // the default is just to accept the brand itself 377 c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1"}) 378 379 encoded = strings.Replace(withTimestamp, serialAuths, "serial-authority:\n - foo\n - bar\n", 1) 380 a, err = asserts.Decode([]byte(encoded)) 381 c.Assert(err, IsNil) 382 model = a.(*asserts.Model) 383 // the brand is always added implicitly 384 c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1", "foo", "bar"}) 385 } 386 387 func (mods *modelSuite) TestDecodeSystemUserAuthorityIsOptional(c *C) { 388 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 389 encoded := strings.Replace(withTimestamp, sysUserAuths, "", 1) 390 a, err := asserts.Decode([]byte(encoded)) 391 c.Assert(err, IsNil) 392 model := a.(*asserts.Model) 393 // the default is just to accept the brand itself 394 c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1"}) 395 396 encoded = strings.Replace(withTimestamp, sysUserAuths, "system-user-authority:\n - foo\n - bar\n", 1) 397 a, err = asserts.Decode([]byte(encoded)) 398 c.Assert(err, IsNil) 399 model = a.(*asserts.Model) 400 // the brand is always added implicitly, it can always sign 401 // a new revision of the model anyway 402 c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1", "foo", "bar"}) 403 } 404 405 func (mods *modelSuite) TestDecodeKernelTrack(c *C) { 406 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 407 encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux=18\n", 1) 408 a, err := asserts.Decode([]byte(encoded)) 409 c.Assert(err, IsNil) 410 model := a.(*asserts.Model) 411 c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{ 412 Name: "baz-linux", 413 SnapType: "kernel", 414 Modes: []string{"run"}, 415 PinnedTrack: "18", 416 Presence: "required", 417 }) 418 c.Check(model.Kernel(), Equals, "baz-linux") 419 c.Check(model.KernelTrack(), Equals, "18") 420 } 421 422 func (mods *modelSuite) TestDecodeGadgetTrack(c *C) { 423 withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 424 encoded := strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget=18\n", 1) 425 a, err := asserts.Decode([]byte(encoded)) 426 c.Assert(err, IsNil) 427 model := a.(*asserts.Model) 428 c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{ 429 Name: "brand-gadget", 430 SnapType: "gadget", 431 Modes: []string{"run"}, 432 PinnedTrack: "18", 433 Presence: "required", 434 }) 435 c.Check(model.Gadget(), Equals, "brand-gadget") 436 c.Check(model.GadgetTrack(), Equals, "18") 437 } 438 439 const ( 440 modelErrPrefix = "assertion model: " 441 ) 442 443 func (mods *modelSuite) TestDecodeInvalid(c *C) { 444 encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) 445 446 invalidTests := []struct{ original, invalid, expectedErr string }{ 447 {"series: 16\n", "", `"series" header is mandatory`}, 448 {"series: 16\n", "series: \n", `"series" header should not be empty`}, 449 {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, 450 {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, 451 {"brand-id: brand-id1\n", "brand-id: random\n", `authority-id and brand-id must match, model assertions are expected to be signed by the brand: "brand-id1" != "random"`}, 452 {"model: baz-3000\n", "", `"model" header is mandatory`}, 453 {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, 454 {"model: baz-3000\n", "model: baz/3000\n", `"model" primary key header cannot contain '/'`}, 455 // lift this restriction at a later point 456 {"model: baz-3000\n", "model: BAZ-3000\n", `"model" header cannot contain uppercase letters`}, 457 {"display-name: Baz 3000\n", "display-name:\n - xyz\n", `"display-name" header must be a string`}, 458 {"architecture: amd64\n", "", `"architecture" header is mandatory`}, 459 {"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`}, 460 {"gadget: brand-gadget\n", "", `"gadget" header is mandatory`}, 461 {"gadget: brand-gadget\n", "gadget: \n", `"gadget" header should not be empty`}, 462 {"gadget: brand-gadget\n", "gadget: brand-gadget=x/x/x\n", `"gadget" channel selector must be a track name only`}, 463 {"gadget: brand-gadget\n", "gadget: brand-gadget=stable\n", `"gadget" channel selector must be a track name`}, 464 {"gadget: brand-gadget\n", "gadget: brand-gadget=18/beta\n", `"gadget" channel selector must be a track name only`}, 465 {"gadget: brand-gadget\n", "gadget:\n - xyz \n", `"gadget" header must be a string`}, 466 {"kernel: baz-linux\n", "", `"kernel" header is mandatory`}, 467 {"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`}, 468 {"kernel: baz-linux\n", "kernel: baz-linux=x/x/x\n", `"kernel" channel selector must be a track name only`}, 469 {"kernel: baz-linux\n", "kernel: baz-linux=stable\n", `"kernel" channel selector must be a track name`}, 470 {"kernel: baz-linux\n", "kernel: baz-linux=18/beta\n", `"kernel" channel selector must be a track name only`}, 471 {"kernel: baz-linux\n", "kernel:\n - xyz \n", `"kernel" header must be a string`}, 472 {"base: core18\n", "base:\n - xyz \n", `"base" header must be a string`}, 473 {"store: brand-store\n", "store:\n - xyz\n", `"store" header must be a string`}, 474 {mods.tsLine, "", `"timestamp" header is mandatory`}, 475 {mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, 476 {mods.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, 477 {reqSnaps, "required-snaps: foo\n", `"required-snaps" header must be a list of strings`}, 478 {reqSnaps, "required-snaps:\n -\n - nested\n", `"required-snaps" header must be a list of strings`}, 479 {serialAuths, "serial-authority:\n a: 1\n", `"serial-authority" header must be a list of account ids`}, 480 {serialAuths, "serial-authority:\n - 5_6\n", `"serial-authority" header must be a list of account ids`}, 481 {sysUserAuths, "system-user-authority:\n a: 1\n", `"system-user-authority" header must be '\*' or a list of account ids`}, 482 {sysUserAuths, "system-user-authority:\n - 5_6\n", `"system-user-authority" header must be '\*' or a list of account ids`}, 483 {reqSnaps, "grade: dangerous\n", `cannot specify a grade for model without the extended snaps header`}, 484 } 485 486 for _, test := range invalidTests { 487 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 488 _, err := asserts.Decode([]byte(invalid)) 489 c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr) 490 } 491 } 492 493 func (mods *modelSuite) TestModelCheck(c *C) { 494 ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1))) 495 c.Assert(err, IsNil) 496 497 storeDB, db := makeStoreAndCheckDB(c) 498 brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db) 499 500 headers := ex.Headers() 501 headers["brand-id"] = brandDB.AuthorityID 502 headers["timestamp"] = time.Now().Format(time.RFC3339) 503 model, err := brandDB.Sign(asserts.ModelType, headers, nil, "") 504 c.Assert(err, IsNil) 505 506 err = db.Check(model) 507 c.Assert(err, IsNil) 508 } 509 510 func (mods *modelSuite) TestModelCheckInconsistentTimestamp(c *C) { 511 ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1))) 512 c.Assert(err, IsNil) 513 514 storeDB, db := makeStoreAndCheckDB(c) 515 brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db) 516 517 headers := ex.Headers() 518 headers["brand-id"] = brandDB.AuthorityID 519 headers["timestamp"] = "2011-01-01T14:00:00Z" 520 model, err := brandDB.Sign(asserts.ModelType, headers, nil, "") 521 c.Assert(err, IsNil) 522 523 err = db.Check(model) 524 c.Assert(err, ErrorMatches, `model assertion timestamp "2011-01-01 14:00:00 \+0000 UTC" outside of signing key validity \(key valid since.*\)`) 525 } 526 527 func (mods *modelSuite) TestClassicDecodeOK(c *C) { 528 encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1) 529 a, err := asserts.Decode([]byte(encoded)) 530 c.Assert(err, IsNil) 531 c.Check(a.Type(), Equals, asserts.ModelType) 532 model := a.(*asserts.Model) 533 c.Check(model.AuthorityID(), Equals, "brand-id1") 534 c.Check(model.Timestamp(), Equals, mods.ts) 535 c.Check(model.Series(), Equals, "16") 536 c.Check(model.BrandID(), Equals, "brand-id1") 537 c.Check(model.Model(), Equals, "baz-3000") 538 c.Check(model.DisplayName(), Equals, "Baz 3000") 539 c.Check(model.Classic(), Equals, true) 540 c.Check(model.Architecture(), Equals, "amd64") 541 c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{ 542 Name: "brand-gadget", 543 SnapType: "gadget", 544 Modes: []string{"run"}, 545 Presence: "required", 546 }) 547 c.Check(model.Gadget(), Equals, "brand-gadget") 548 c.Check(model.KernelSnap(), IsNil) 549 c.Check(model.Kernel(), Equals, "") 550 c.Check(model.KernelTrack(), Equals, "") 551 c.Check(model.Base(), Equals, "") 552 c.Check(model.BaseSnap(), IsNil) 553 c.Check(model.Store(), Equals, "brand-store") 554 essentialSnaps := model.EssentialSnaps() 555 c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{ 556 model.GadgetSnap(), 557 }) 558 snaps := model.SnapsWithoutEssential() 559 c.Check(snaps, DeepEquals, []*asserts.ModelSnap{ 560 { 561 Name: "foo", 562 Modes: []string{"run"}, 563 Presence: "required", 564 }, 565 { 566 Name: "bar", 567 Modes: []string{"run"}, 568 Presence: "required", 569 }, 570 }) 571 // gadget included 572 reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps()) 573 for _, e := range essentialSnaps { 574 c.Check(reqSnaps.Contains(e), Equals, true) 575 } 576 for _, s := range snaps { 577 c.Check(reqSnaps.Contains(s), Equals, true) 578 } 579 c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps)) 580 // gadget excluded 581 noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps()) 582 for _, e := range essentialSnaps { 583 c.Check(noEssential.Contains(e), Equals, false) 584 } 585 for _, s := range snaps { 586 c.Check(noEssential.Contains(s), Equals, true) 587 } 588 c.Check(noEssential.Size(), Equals, len(snaps)) 589 } 590 591 func (mods *modelSuite) TestClassicDecodeInvalid(c *C) { 592 encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1) 593 594 invalidTests := []struct{ original, invalid, expectedErr string }{ 595 {"classic: true\n", "classic: foo\n", `"classic" header must be 'true' or 'false'`}, 596 {"architecture: amd64\n", "architecture:\n - foo\n", `"architecture" header must be a string`}, 597 {"gadget: brand-gadget\n", "gadget:\n - foo\n", `"gadget" header must be a string`}, 598 {"gadget: brand-gadget\n", "kernel: brand-kernel\n", `cannot specify a kernel with a classic model`}, 599 {"gadget: brand-gadget\n", "base: some-base\n", `cannot specify a base with a classic model`}, 600 {"gadget: brand-gadget\n", "gadget:\n - xyz\n", `"gadget" header must be a string`}, 601 } 602 603 for _, test := range invalidTests { 604 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 605 _, err := asserts.Decode([]byte(invalid)) 606 c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr) 607 } 608 } 609 610 func (mods *modelSuite) TestClassicDecodeGadgetAndArchOptional(c *C) { 611 encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1) 612 encoded = strings.Replace(encoded, "gadget: brand-gadget\n", "", 1) 613 encoded = strings.Replace(encoded, "architecture: amd64\n", "", 1) 614 a, err := asserts.Decode([]byte(encoded)) 615 c.Assert(err, IsNil) 616 c.Check(a.Type(), Equals, asserts.ModelType) 617 model := a.(*asserts.Model) 618 c.Check(model.Classic(), Equals, true) 619 c.Check(model.Architecture(), Equals, "") 620 c.Check(model.GadgetSnap(), IsNil) 621 c.Check(model.Gadget(), Equals, "") 622 c.Check(model.GadgetTrack(), Equals, "") 623 } 624 625 func (mods *modelSuite) TestCore20DecodeOK(c *C) { 626 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 627 encoded = strings.Replace(encoded, "OTHER", "", 1) 628 a, err := asserts.Decode([]byte(encoded)) 629 c.Assert(err, IsNil) 630 c.Check(a.Type(), Equals, asserts.ModelType) 631 model := a.(*asserts.Model) 632 c.Check(model.AuthorityID(), Equals, "brand-id1") 633 c.Check(model.Timestamp(), Equals, mods.ts) 634 c.Check(model.Series(), Equals, "16") 635 c.Check(model.BrandID(), Equals, "brand-id1") 636 c.Check(model.Model(), Equals, "baz-3000") 637 c.Check(model.DisplayName(), Equals, "Baz 3000") 638 c.Check(model.Architecture(), Equals, "amd64") 639 c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{ 640 Name: "brand-gadget", 641 SnapID: "brandgadgetdidididididididididid", 642 SnapType: "gadget", 643 Modes: []string{"run", "ephemeral"}, 644 DefaultChannel: "latest/stable", 645 Presence: "required", 646 }) 647 c.Check(model.Gadget(), Equals, "brand-gadget") 648 c.Check(model.GadgetTrack(), Equals, "") 649 c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{ 650 Name: "baz-linux", 651 SnapID: "bazlinuxidididididididididididid", 652 SnapType: "kernel", 653 Modes: []string{"run", "ephemeral"}, 654 DefaultChannel: "20", 655 Presence: "required", 656 }) 657 c.Check(model.Kernel(), Equals, "baz-linux") 658 c.Check(model.KernelTrack(), Equals, "") 659 c.Check(model.Base(), Equals, "core20") 660 c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{ 661 Name: "core20", 662 SnapID: naming.WellKnownSnapID("core20"), 663 SnapType: "base", 664 Modes: []string{"run", "ephemeral"}, 665 DefaultChannel: "latest/stable", 666 Presence: "required", 667 }) 668 c.Check(model.Store(), Equals, "brand-store") 669 c.Check(model.Grade(), Equals, asserts.ModelSecured) 670 c.Check(model.StorageSafety(), Equals, asserts.StorageSafetyEncrypted) 671 essentialSnaps := model.EssentialSnaps() 672 c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{ 673 model.KernelSnap(), 674 model.BaseSnap(), 675 model.GadgetSnap(), 676 }) 677 snaps := model.SnapsWithoutEssential() 678 c.Check(snaps, DeepEquals, []*asserts.ModelSnap{ 679 { 680 Name: "other-base", 681 SnapID: "otherbasedididididididididididid", 682 SnapType: "base", 683 Modes: []string{"run"}, 684 DefaultChannel: "latest/stable", 685 Presence: "required", 686 }, 687 { 688 Name: "nm", 689 SnapID: "nmididididididididididididididid", 690 SnapType: "app", 691 Modes: []string{"ephemeral", "run"}, 692 DefaultChannel: "1.0", 693 Presence: "required", 694 }, 695 { 696 Name: "myapp", 697 SnapID: "myappdididididididididididididid", 698 SnapType: "app", 699 Modes: []string{"run"}, 700 DefaultChannel: "2.0", 701 Presence: "required", 702 }, 703 { 704 Name: "myappopt", 705 SnapID: "myappoptidididididididididididid", 706 SnapType: "app", 707 Modes: []string{"run"}, 708 DefaultChannel: "latest/stable", 709 Presence: "optional", 710 }, 711 }) 712 // essential snaps included 713 reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps()) 714 for _, e := range essentialSnaps { 715 c.Check(reqSnaps.Contains(e), Equals, true) 716 } 717 for _, s := range snaps { 718 c.Check(reqSnaps.Contains(s), Equals, s.Presence == "required") 719 } 720 c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps)-1) 721 // essential snaps excluded 722 noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps()) 723 for _, e := range essentialSnaps { 724 c.Check(noEssential.Contains(e), Equals, false) 725 } 726 for _, s := range snaps { 727 c.Check(noEssential.Contains(s), Equals, s.Presence == "required") 728 } 729 c.Check(noEssential.Size(), Equals, len(snaps)-1) 730 731 c.Check(model.SystemUserAuthority(), HasLen, 0) 732 c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1"}) 733 } 734 735 func (mods *modelSuite) TestCore20ExplictBootBase(c *C) { 736 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 737 encoded = strings.Replace(encoded, "OTHER", ` - 738 name: core20 739 id: core20ididididididididididididid 740 type: base 741 default-channel: latest/candidate 742 `, 1) 743 a, err := asserts.Decode([]byte(encoded)) 744 c.Assert(err, IsNil) 745 c.Check(a.Type(), Equals, asserts.ModelType) 746 model := a.(*asserts.Model) 747 c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{ 748 Name: "core20", 749 SnapID: "core20ididididididididididididid", 750 SnapType: "base", 751 Modes: []string{"run", "ephemeral"}, 752 DefaultChannel: "latest/candidate", 753 Presence: "required", 754 }) 755 } 756 757 func (mods *modelSuite) TestCore20ExplictSnapd(c *C) { 758 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 759 encoded = strings.Replace(encoded, "OTHER", ` - 760 name: snapd 761 id: snapdidididididididididididididd 762 type: snapd 763 default-channel: latest/edge 764 `, 1) 765 a, err := asserts.Decode([]byte(encoded)) 766 c.Assert(err, IsNil) 767 c.Check(a.Type(), Equals, asserts.ModelType) 768 model := a.(*asserts.Model) 769 snapdSnap := model.EssentialSnaps()[0] 770 c.Check(snapdSnap, DeepEquals, &asserts.ModelSnap{ 771 Name: "snapd", 772 SnapID: "snapdidididididididididididididd", 773 SnapType: "snapd", 774 Modes: []string{"run", "ephemeral"}, 775 DefaultChannel: "latest/edge", 776 Presence: "required", 777 }) 778 } 779 780 func (mods *modelSuite) TestCore20GradeOptionalDefaultSigned(c *C) { 781 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 782 encoded = strings.Replace(encoded, "OTHER", "", 1) 783 encoded = strings.Replace(encoded, "grade: secured\n", "", 1) 784 785 a, err := asserts.Decode([]byte(encoded)) 786 c.Assert(err, IsNil) 787 c.Check(a.Type(), Equals, asserts.ModelType) 788 model := a.(*asserts.Model) 789 c.Check(model.Grade(), Equals, asserts.ModelSigned) 790 } 791 792 func (mods *modelSuite) TestCore20ValidGrades(c *C) { 793 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 794 encoded = strings.Replace(encoded, "OTHER", "", 1) 795 796 for _, grade := range []string{"signed", "secured", "dangerous"} { 797 ex := strings.Replace(encoded, "grade: secured\n", fmt.Sprintf("grade: %s\n", grade), 1) 798 a, err := asserts.Decode([]byte(ex)) 799 c.Assert(err, IsNil) 800 c.Check(a.Type(), Equals, asserts.ModelType) 801 model := a.(*asserts.Model) 802 c.Check(string(model.Grade()), Equals, grade) 803 } 804 } 805 806 func (mods *modelSuite) TestModelGradeCode(c *C) { 807 for i, grade := range []asserts.ModelGrade{asserts.ModelGradeUnset, asserts.ModelDangerous, asserts.ModelSigned, asserts.ModelSecured} { 808 // unset is represented as zero 809 code := 0 810 if i > 0 { 811 // have some space between grades to add new ones 812 n := (i - 1) * 8 813 if n == 0 { 814 n = 1 // dangerous 815 } 816 // lower 16 bits are reserved 817 code = n << 16 818 } 819 c.Check(grade.Code(), Equals, uint32(code)) 820 } 821 } 822 823 func (mods *modelSuite) TestCore20GradeDangerous(c *C) { 824 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 825 encoded = strings.Replace(encoded, "OTHER", "", 1) 826 encoded = strings.Replace(encoded, "grade: secured\n", "grade: dangerous\n", 1) 827 // snap ids are optional with grade dangerous to allow working 828 // with local/not pushed yet to the store snaps 829 encoded = strings.Replace(encoded, " id: myappdididididididididididididid\n", "", 1) 830 encoded = strings.Replace(encoded, " id: brandgadgetdidididididididididid\n", "", 1) 831 a, err := asserts.Decode([]byte(encoded)) 832 c.Assert(err, IsNil) 833 c.Check(a.Type(), Equals, asserts.ModelType) 834 model := a.(*asserts.Model) 835 c.Check(model.Grade(), Equals, asserts.ModelDangerous) 836 snaps := model.SnapsWithoutEssential() 837 c.Check(snaps[len(snaps)-2], DeepEquals, &asserts.ModelSnap{ 838 Name: "myapp", 839 SnapType: "app", 840 Modes: []string{"run"}, 841 DefaultChannel: "2.0", 842 Presence: "required", 843 }) 844 } 845 846 func (mods *modelSuite) TestCore20ValidStorageSafety(c *C) { 847 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 848 encoded = strings.Replace(encoded, "OTHER", "", 1) 849 encoded = strings.Replace(encoded, "grade: secured\n", "grade: signed\n", 1) 850 851 for _, tc := range []struct { 852 ss asserts.StorageSafety 853 sss string 854 }{ 855 {asserts.StorageSafetyPreferEncrypted, "prefer-encrypted"}, 856 {asserts.StorageSafetyPreferUnencrypted, "prefer-unencrypted"}, 857 {asserts.StorageSafetyEncrypted, "encrypted"}, 858 } { 859 ex := strings.Replace(encoded, "storage-safety: encrypted\n", fmt.Sprintf("storage-safety: %s\n", tc.sss), 1) 860 a, err := asserts.Decode([]byte(ex)) 861 c.Assert(err, IsNil) 862 c.Check(a.Type(), Equals, asserts.ModelType) 863 model := a.(*asserts.Model) 864 c.Check(model.StorageSafety(), Equals, tc.ss) 865 } 866 } 867 868 func (mods *modelSuite) TestCore20DefaultStorageSafetySecured(c *C) { 869 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 870 encoded = strings.Replace(encoded, "OTHER", "", 1) 871 ex := strings.Replace(encoded, "storage-safety: encrypted\n", "", 1) 872 873 a, err := asserts.Decode([]byte(ex)) 874 c.Assert(err, IsNil) 875 c.Check(a.Type(), Equals, asserts.ModelType) 876 model := a.(*asserts.Model) 877 c.Check(model.StorageSafety(), Equals, asserts.StorageSafetyEncrypted) 878 } 879 880 func (mods *modelSuite) TestCore20DefaultStorageSafetySignedDangerous(c *C) { 881 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 882 encoded = strings.Replace(encoded, "OTHER", "", 1) 883 encoded = strings.Replace(encoded, "storage-safety: encrypted\n", "", 1) 884 885 for _, grade := range []string{"dangerous", "signed"} { 886 ex := strings.Replace(encoded, "grade: secured\n", fmt.Sprintf("grade: %s\n", grade), 1) 887 a, err := asserts.Decode([]byte(ex)) 888 c.Assert(err, IsNil) 889 c.Check(a.Type(), Equals, asserts.ModelType) 890 model := a.(*asserts.Model) 891 c.Check(model.StorageSafety(), Equals, asserts.StorageSafetyPreferEncrypted) 892 } 893 } 894 895 func (mods *modelSuite) TestCore20DecodeInvalid(c *C) { 896 encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1) 897 898 snapsStanza := encoded[strings.Index(encoded, "snaps:"):strings.Index(encoded, "grade:")] 899 900 invalidTests := []struct{ original, invalid, expectedErr string }{ 901 {"base: core20\n", "", `"base" header is mandatory`}, 902 {"base: core20\n", "base: alt-base\n", `cannot specify not well-known base "alt-base" without a corresponding "snaps" header entry`}, 903 {"OTHER", "classic: true\n", `cannot use extended snaps header for a classic model \(yet\)`}, 904 {snapsStanza, "snaps: snap\n", `"snaps" header must be a list of maps`}, 905 {snapsStanza, "snaps:\n - snap\n", `"snaps" header must be a list of maps`}, 906 {"name: myapp\n", "other: 1\n", `"name" of snap is mandatory`}, 907 {"name: myapp\n", "name: myapp_2\n", `invalid snap name "myapp_2"`}, 908 {"id: myappdididididididididididididid\n", "id: 2\n", `"id" of snap "myapp" contains invalid characters: "2"`}, 909 {" id: myappdididididididididididididid\n", "", `"id" of snap "myapp" is mandatory for secured grade model`}, 910 {"type: gadget\n", "type:\n - g\n", `"type" of snap "brand-gadget" must be a string`}, 911 {"type: app\n", "type: thing\n", `"type" of snap "myappopt" must be one of must be one of app|base|gadget|kernel|core|snapd`}, 912 {"modes:\n - run\n", "modes: run\n", `"modes" of snap "other-base" must be a list of strings`}, 913 {"default-channel: 20\n", "default-channel: edge\n", `default channel for snap "baz-linux" must specify a track`}, 914 {"default-channel: 2.0\n", "default-channel:\n - x\n", `"default-channel" of snap "myapp" must be a string`}, 915 {"default-channel: 2.0\n", "default-channel: 2.0/xyz/z\n", `invalid default channel for snap "myapp": invalid risk in channel name: 2.0/xyz/z`}, 916 {"presence: optional\n", "presence:\n - opt\n", `"presence" of snap "myappopt" must be a string`}, 917 {"presence: optional\n", "presence: no\n", `"presence" of snap "myappopt" must be one of must be one of required|optional`}, 918 {"OTHER", " -\n name: myapp\n id: myappdididididididididididididid\n", `cannot list the same snap "myapp" multiple times`}, 919 {"OTHER", " -\n name: myapp2\n id: myappdididididididididididididid\n", `cannot specify the same snap id "myappdididididididididididididid" multiple times, specified for snaps "myapp" and "myapp2"`}, 920 {"OTHER", " -\n name: kernel2\n id: kernel2didididididididididididid\n type: kernel\n", `cannot specify multiple kernel snaps: "baz-linux" and "kernel2"`}, 921 {"OTHER", " -\n name: gadget2\n id: gadget2didididididididididididid\n type: gadget\n", `cannot specify multiple gadget snaps: "brand-gadget" and "gadget2"`}, 922 {"type: gadget\n", "type: gadget\n presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`}, 923 {"type: gadget\n", "type: gadget\n modes:\n - run\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`}, 924 {"type: kernel\n", "type: kernel\n presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "baz-linux"`}, 925 {"OTHER", " -\n name: core20\n id: core20ididididididididididididid\n type: base\n presence: optional\n", `essential snaps are always available, cannot specify modes or presence for snap "core20"`}, 926 {"type: gadget\n", "type: app\n", `one "snaps" header entry must specify the model gadget`}, 927 {"type: kernel\n", "type: app\n", `one "snaps" header entry must specify the model kernel`}, 928 {"OTHER", " -\n name: core20\n id: core20ididididididididididididid\n type: app\n", `boot base "core20" must specify type "base", not "app"`}, 929 {"OTHER", "kernel: foo\n", `cannot specify separate "kernel" header once using the extended snaps header`}, 930 {"OTHER", "gadget: foo\n", `cannot specify separate "gadget" header once using the extended snaps header`}, 931 {"OTHER", "required-snaps:\n - foo\n", `cannot specify separate "required-snaps" header once using the extended snaps header`}, 932 {"grade: secured\n", "grade: foo\n", `grade for model must be secured|signed|dangerous`}, 933 {"storage-safety: encrypted\n", "storage-safety: foo\n", `storage-safety for model must be encrypted\|prefer-encrypted\|prefer-unencrypted, not "foo"`}, 934 {"storage-safety: encrypted\n", "storage-safety: prefer-unencrypted\n", `secured grade model must not have storage-safety overridden, only "encrypted" is valid`}, 935 } 936 for _, test := range invalidTests { 937 invalid := strings.Replace(encoded, test.original, test.invalid, 1) 938 invalid = strings.Replace(invalid, "OTHER", "", 1) 939 _, err := asserts.Decode([]byte(invalid)) 940 c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr) 941 } 942 }