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