github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/batch_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2021 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 "bytes" 24 "fmt" 25 "time" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/asserts/assertstest" 31 ) 32 33 type batchSuite struct { 34 storeSigning *assertstest.StoreStack 35 dev1Acct *asserts.Account 36 37 db *asserts.Database 38 } 39 40 var _ = Suite(&batchSuite{}) 41 42 func (s *batchSuite) SetUpTest(c *C) { 43 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 44 45 s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "") 46 err := s.storeSigning.Add(s.dev1Acct) 47 c.Assert(err, IsNil) 48 49 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 50 Backstore: asserts.NewMemoryBackstore(), 51 Trusted: s.storeSigning.Trusted, 52 }) 53 c.Assert(err, IsNil) 54 s.db = db 55 } 56 57 func (s *batchSuite) snapDecl(c *C, name string, extraHeaders map[string]interface{}) *asserts.SnapDeclaration { 58 headers := map[string]interface{}{ 59 "series": "16", 60 "snap-id": name + "-id", 61 "snap-name": name, 62 "publisher-id": s.dev1Acct.AccountID(), 63 "timestamp": time.Now().Format(time.RFC3339), 64 } 65 for h, v := range extraHeaders { 66 headers[h] = v 67 } 68 decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 69 c.Assert(err, IsNil) 70 err = s.storeSigning.Add(decl) 71 c.Assert(err, IsNil) 72 return decl.(*asserts.SnapDeclaration) 73 } 74 75 func (s *batchSuite) TestAddStream(c *C) { 76 b := &bytes.Buffer{} 77 enc := asserts.NewEncoder(b) 78 // wrong order is ok 79 err := enc.Encode(s.dev1Acct) 80 c.Assert(err, IsNil) 81 enc.Encode(s.storeSigning.StoreAccountKey("")) 82 c.Assert(err, IsNil) 83 84 batch := asserts.NewBatch(nil) 85 refs, err := batch.AddStream(b) 86 c.Assert(err, IsNil) 87 c.Check(refs, DeepEquals, []*asserts.Ref{ 88 {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}}, 89 {Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}}, 90 }) 91 92 // noop 93 err = batch.Add(s.storeSigning.StoreAccountKey("")) 94 c.Assert(err, IsNil) 95 96 err = batch.CommitTo(s.db, nil) 97 c.Assert(err, IsNil) 98 99 devAcct, err := s.db.Find(asserts.AccountType, map[string]string{ 100 "account-id": s.dev1Acct.AccountID(), 101 }) 102 c.Assert(err, IsNil) 103 c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") 104 } 105 106 func (s *batchSuite) TestCommitToAndObserve(c *C) { 107 b := &bytes.Buffer{} 108 enc := asserts.NewEncoder(b) 109 // wrong order is ok 110 err := enc.Encode(s.dev1Acct) 111 c.Assert(err, IsNil) 112 enc.Encode(s.storeSigning.StoreAccountKey("")) 113 c.Assert(err, IsNil) 114 115 batch := asserts.NewBatch(nil) 116 refs, err := batch.AddStream(b) 117 c.Assert(err, IsNil) 118 c.Check(refs, DeepEquals, []*asserts.Ref{ 119 {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}}, 120 {Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}}, 121 }) 122 123 // noop 124 err = batch.Add(s.storeSigning.StoreAccountKey("")) 125 c.Assert(err, IsNil) 126 127 var seen []*asserts.Ref 128 obs := func(verified asserts.Assertion) { 129 seen = append(seen, verified.Ref()) 130 } 131 err = batch.CommitToAndObserve(s.db, obs, nil) 132 c.Assert(err, IsNil) 133 134 devAcct, err := s.db.Find(asserts.AccountType, map[string]string{ 135 "account-id": s.dev1Acct.AccountID(), 136 }) 137 c.Assert(err, IsNil) 138 c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") 139 140 // this is the order they needed to be added 141 c.Check(seen, DeepEquals, []*asserts.Ref{ 142 {Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}}, 143 {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}}, 144 }) 145 } 146 147 func (s *batchSuite) TestAddEmptyStream(c *C) { 148 b := &bytes.Buffer{} 149 150 batch := asserts.NewBatch(nil) 151 refs, err := batch.AddStream(b) 152 c.Assert(err, IsNil) 153 c.Check(refs, HasLen, 0) 154 } 155 156 func (s *batchSuite) TestConsiderPreexisting(c *C) { 157 // prereq store key 158 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 159 c.Assert(err, IsNil) 160 161 batch := asserts.NewBatch(nil) 162 err = batch.Add(s.dev1Acct) 163 c.Assert(err, IsNil) 164 165 err = batch.CommitTo(s.db, nil) 166 c.Assert(err, IsNil) 167 168 devAcct, err := s.db.Find(asserts.AccountType, map[string]string{ 169 "account-id": s.dev1Acct.AccountID(), 170 }) 171 c.Assert(err, IsNil) 172 c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") 173 } 174 175 func (s *batchSuite) TestAddStreamReturnsEffectivelyAddedRefs(c *C) { 176 batch := asserts.NewBatch(nil) 177 178 err := batch.Add(s.storeSigning.StoreAccountKey("")) 179 c.Assert(err, IsNil) 180 181 b := &bytes.Buffer{} 182 enc := asserts.NewEncoder(b) 183 // wrong order is ok 184 err = enc.Encode(s.dev1Acct) 185 c.Assert(err, IsNil) 186 // this was already added to the batch 187 enc.Encode(s.storeSigning.StoreAccountKey("")) 188 c.Assert(err, IsNil) 189 190 // effectively adds only the developer1 account 191 refs, err := batch.AddStream(b) 192 c.Assert(err, IsNil) 193 c.Check(refs, DeepEquals, []*asserts.Ref{ 194 {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}}, 195 }) 196 197 err = batch.CommitTo(s.db, nil) 198 c.Assert(err, IsNil) 199 200 devAcct, err := s.db.Find(asserts.AccountType, map[string]string{ 201 "account-id": s.dev1Acct.AccountID(), 202 }) 203 c.Assert(err, IsNil) 204 c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1") 205 } 206 207 func (s *batchSuite) TestCommitRefusesSelfSignedKey(c *C) { 208 aKey, _ := assertstest.GenerateKey(752) 209 aSignDB := assertstest.NewSigningDB("can0nical", aKey) 210 211 aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey()) 212 c.Assert(err, IsNil) 213 214 headers := map[string]interface{}{ 215 "authority-id": "can0nical", 216 "account-id": "can0nical", 217 "public-key-sha3-384": aKey.PublicKey().ID(), 218 "name": "default", 219 "since": time.Now().UTC().Format(time.RFC3339), 220 } 221 acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "") 222 c.Assert(err, IsNil) 223 224 headers = map[string]interface{}{ 225 "authority-id": "can0nical", 226 "brand-id": "can0nical", 227 "repair-id": "2", 228 "summary": "repair two", 229 "timestamp": time.Now().UTC().Format(time.RFC3339), 230 } 231 repair, err := aSignDB.Sign(asserts.RepairType, headers, []byte("#script"), "") 232 c.Assert(err, IsNil) 233 234 batch := asserts.NewBatch(nil) 235 236 err = batch.Add(repair) 237 c.Assert(err, IsNil) 238 239 err = batch.Add(acctKey) 240 c.Assert(err, IsNil) 241 242 // this must fail 243 err = batch.CommitTo(s.db, nil) 244 c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`) 245 } 246 247 func (s *batchSuite) TestAddUnsupported(c *C) { 248 restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111) 249 defer restore() 250 251 batch := asserts.NewBatch(nil) 252 253 var a asserts.Assertion 254 (func() { 255 restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999) 256 defer restore() 257 headers := map[string]interface{}{ 258 "format": "999", 259 "revision": "1", 260 "series": "16", 261 "snap-id": "snap-id-1", 262 "snap-name": "foo", 263 "publisher-id": s.dev1Acct.AccountID(), 264 "timestamp": time.Now().Format(time.RFC3339), 265 } 266 var err error 267 a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 268 c.Assert(err, IsNil) 269 })() 270 271 err := batch.Add(a) 272 c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`) 273 } 274 275 func (s *batchSuite) TestAddUnsupportedIgnore(c *C) { 276 restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111) 277 defer restore() 278 279 var uRef *asserts.Ref 280 unsupported := func(ref *asserts.Ref, _ error) error { 281 uRef = ref 282 return nil 283 } 284 285 batch := asserts.NewBatch(unsupported) 286 287 var a asserts.Assertion 288 (func() { 289 restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999) 290 defer restore() 291 headers := map[string]interface{}{ 292 "format": "999", 293 "revision": "1", 294 "series": "16", 295 "snap-id": "snap-id-1", 296 "snap-name": "foo", 297 "publisher-id": s.dev1Acct.AccountID(), 298 "timestamp": time.Now().Format(time.RFC3339), 299 } 300 var err error 301 a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "") 302 c.Assert(err, IsNil) 303 })() 304 305 err := batch.Add(a) 306 c.Check(err, IsNil) 307 c.Check(uRef, DeepEquals, &asserts.Ref{ 308 Type: asserts.SnapDeclarationType, 309 PrimaryKey: []string{"16", "snap-id-1"}, 310 }) 311 } 312 313 func (s *batchSuite) TestCommitPartial(c *C) { 314 // Commit does add any successful assertion until the first error 315 316 // store key already present 317 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 318 c.Assert(err, IsNil) 319 320 batch := asserts.NewBatch(nil) 321 322 snapDeclFoo := s.snapDecl(c, "foo", nil) 323 324 err = batch.Add(snapDeclFoo) 325 c.Assert(err, IsNil) 326 err = batch.Add(s.dev1Acct) 327 c.Assert(err, IsNil) 328 329 // too old 330 rev := 1 331 headers := map[string]interface{}{ 332 "snap-id": "foo-id", 333 "snap-sha3-384": makeDigest(rev), 334 "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))), 335 "snap-revision": fmt.Sprintf("%d", rev), 336 "developer-id": s.dev1Acct.AccountID(), 337 "timestamp": time.Time{}.Format(time.RFC3339), 338 } 339 snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "") 340 c.Assert(err, IsNil) 341 342 err = batch.Add(snapRev) 343 c.Assert(err, IsNil) 344 345 err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: false}) 346 c.Check(err, ErrorMatches, `(?ms).*validity.*`) 347 348 // snap-declaration was added anyway 349 _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{ 350 "series": "16", 351 "snap-id": "foo-id", 352 }) 353 c.Assert(err, IsNil) 354 } 355 356 func (s *batchSuite) TestCommitMissing(c *C) { 357 // store key already present 358 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 359 c.Assert(err, IsNil) 360 361 batch := asserts.NewBatch(nil) 362 363 snapDeclFoo := s.snapDecl(c, "foo", nil) 364 365 err = batch.Add(snapDeclFoo) 366 c.Assert(err, IsNil) 367 368 err = batch.CommitTo(s.db, nil) 369 c.Check(err, ErrorMatches, `cannot resolve prerequisite assertion: account.*`) 370 } 371 372 func (s *batchSuite) TestPrecheckPartial(c *C) { 373 // store key already present 374 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 375 c.Assert(err, IsNil) 376 377 batch := asserts.NewBatch(nil) 378 379 snapDeclFoo := s.snapDecl(c, "foo", nil) 380 381 err = batch.Add(snapDeclFoo) 382 c.Assert(err, IsNil) 383 err = batch.Add(s.dev1Acct) 384 c.Assert(err, IsNil) 385 386 // too old 387 rev := 1 388 headers := map[string]interface{}{ 389 "snap-id": "foo-id", 390 "snap-sha3-384": makeDigest(rev), 391 "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))), 392 "snap-revision": fmt.Sprintf("%d", rev), 393 "developer-id": s.dev1Acct.AccountID(), 394 "timestamp": time.Time{}.Format(time.RFC3339), 395 } 396 snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "") 397 c.Assert(err, IsNil) 398 399 err = batch.Add(snapRev) 400 c.Assert(err, IsNil) 401 402 err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true}) 403 c.Check(err, ErrorMatches, `(?ms).*validity.*`) 404 405 // nothing was added 406 _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{ 407 "series": "16", 408 "snap-id": "foo-id", 409 }) 410 c.Assert(asserts.IsNotFound(err), Equals, true) 411 } 412 413 func (s *batchSuite) TestPrecheckHappy(c *C) { 414 // store key already present 415 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 416 c.Assert(err, IsNil) 417 418 batch := asserts.NewBatch(nil) 419 420 snapDeclFoo := s.snapDecl(c, "foo", nil) 421 422 err = batch.Add(snapDeclFoo) 423 c.Assert(err, IsNil) 424 err = batch.Add(s.dev1Acct) 425 c.Assert(err, IsNil) 426 427 rev := 1 428 revDigest := makeDigest(rev) 429 headers := map[string]interface{}{ 430 "snap-id": "foo-id", 431 "snap-sha3-384": revDigest, 432 "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))), 433 "snap-revision": fmt.Sprintf("%d", rev), 434 "developer-id": s.dev1Acct.AccountID(), 435 "timestamp": time.Now().Format(time.RFC3339), 436 } 437 snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "") 438 c.Assert(err, IsNil) 439 440 err = batch.Add(snapRev) 441 c.Assert(err, IsNil) 442 443 // test precheck on its own 444 err = batch.DoPrecheck(s.db) 445 c.Assert(err, IsNil) 446 447 // nothing was added yet 448 _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{ 449 "series": "16", 450 "snap-id": "foo-id", 451 }) 452 c.Assert(asserts.IsNotFound(err), Equals, true) 453 454 // commit (with precheck) 455 err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true}) 456 c.Assert(err, IsNil) 457 458 _, err = s.db.Find(asserts.SnapRevisionType, map[string]string{ 459 "snap-sha3-384": revDigest, 460 }) 461 c.Check(err, IsNil) 462 } 463 464 func (s *batchSuite) TestFetch(c *C) { 465 err := s.db.Add(s.storeSigning.StoreAccountKey("")) 466 c.Assert(err, IsNil) 467 468 s.snapDecl(c, "foo", nil) 469 470 rev := 10 471 revDigest := makeDigest(rev) 472 headers := map[string]interface{}{ 473 "snap-id": "foo-id", 474 "snap-sha3-384": revDigest, 475 "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))), 476 "snap-revision": fmt.Sprintf("%d", rev), 477 "developer-id": s.dev1Acct.AccountID(), 478 "timestamp": time.Now().Format(time.RFC3339), 479 } 480 snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "") 481 c.Assert(err, IsNil) 482 483 err = s.storeSigning.Add(snapRev) 484 c.Assert(err, IsNil) 485 ref := snapRev.Ref() 486 487 batch := asserts.NewBatch(nil) 488 489 // retrieve from storeSigning 490 retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) { 491 return ref.Resolve(s.storeSigning.Find) 492 } 493 // fetching the snap-revision 494 fetching := func(f asserts.Fetcher) error { 495 return f.Fetch(ref) 496 } 497 498 err = batch.Fetch(s.db, retrieve, fetching) 499 c.Assert(err, IsNil) 500 501 // nothing was added yet 502 _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{ 503 "series": "16", 504 "snap-id": "foo-id", 505 }) 506 c.Assert(asserts.IsNotFound(err), Equals, true) 507 508 // commit 509 err = batch.CommitTo(s.db, nil) 510 c.Assert(err, IsNil) 511 512 _, err = s.db.Find(asserts.SnapRevisionType, map[string]string{ 513 "snap-sha3-384": revDigest, 514 }) 515 c.Check(err, IsNil) 516 }