github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/overlord/assertstate/assertstate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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 assertstate implements the manager and state aspects responsible 21 // for the enforcement of assertions in the system and manages the system-wide 22 // assertion database. 23 package assertstate 24 25 import ( 26 "fmt" 27 "strings" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/asserts/snapasserts" 31 "github.com/snapcore/snapd/httputil" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/overlord/snapstate" 34 "github.com/snapcore/snapd/overlord/state" 35 "github.com/snapcore/snapd/release" 36 "github.com/snapcore/snapd/snap" 37 ) 38 39 // Add the given assertion to the system assertion database. 40 func Add(s *state.State, a asserts.Assertion) error { 41 // TODO: deal together with asserts itself with (cascading) side effects of possible assertion updates 42 return cachedDB(s).Add(a) 43 } 44 45 // AddBatch adds the given assertion batch to the system assertion database. 46 func AddBatch(s *state.State, batch *asserts.Batch, opts *asserts.CommitOptions) error { 47 return batch.CommitTo(cachedDB(s), opts) 48 } 49 50 func findError(format string, ref *asserts.Ref, err error) error { 51 if asserts.IsNotFound(err) { 52 return fmt.Errorf(format, ref) 53 } else { 54 return fmt.Errorf(format+": %v", ref, err) 55 } 56 } 57 58 // RefreshSnapDeclarations refetches all the current snap declarations and their prerequisites. 59 func RefreshSnapDeclarations(s *state.State, userID int) error { 60 deviceCtx, err := snapstate.DevicePastSeeding(s, nil) 61 if err != nil { 62 return err 63 } 64 65 snapStates, err := snapstate.All(s) 66 if err != nil { 67 return nil 68 } 69 70 err = bulkRefreshSnapDeclarations(s, snapStates, userID, deviceCtx) 71 if err == nil { 72 // done 73 return nil 74 } 75 if _, ok := err.(*bulkAssertionFallbackError); !ok { 76 // not an error that indicates the server rejecting/failing 77 // the bulk request itself 78 return err 79 } 80 logger.Noticef("bulk refresh of snap-declarations failed, falling back to one-by-one assertion fetching: %v", err) 81 82 modelAs := deviceCtx.Model() 83 84 fetching := func(f asserts.Fetcher) error { 85 for instanceName, snapst := range snapStates { 86 sideInfo := snapst.CurrentSideInfo() 87 if sideInfo.SnapID == "" { 88 continue 89 } 90 if err := snapasserts.FetchSnapDeclaration(f, sideInfo.SnapID); err != nil { 91 if notRetried, ok := err.(*httputil.PersistentNetworkError); ok { 92 return notRetried 93 } 94 return fmt.Errorf("cannot refresh snap-declaration for %q: %v", instanceName, err) 95 } 96 } 97 98 // fetch store assertion if available 99 if modelAs.Store() != "" { 100 err := snapasserts.FetchStore(f, modelAs.Store()) 101 if err != nil && !asserts.IsNotFound(err) { 102 return err 103 } 104 } 105 106 return nil 107 } 108 return doFetch(s, userID, deviceCtx, fetching) 109 } 110 111 type refreshControlError struct { 112 errs []error 113 } 114 115 func (e *refreshControlError) Error() string { 116 if len(e.errs) == 1 { 117 return e.errs[0].Error() 118 } 119 l := []string{""} 120 for _, e := range e.errs { 121 l = append(l, e.Error()) 122 } 123 return fmt.Sprintf("refresh control errors:%s", strings.Join(l, "\n - ")) 124 } 125 126 // ValidateRefreshes validates the refresh candidate revisions represented by 127 // the snapInfos, looking for the needed refresh control validation assertions, 128 // it returns a validated subset in validated and a summary error if not all 129 // candidates validated. ignoreValidation is a set of snap-instance-names that 130 // should not be gated. 131 func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) (validated []*snap.Info, err error) { 132 // maps gated snap-ids to gating snap-ids 133 controlled := make(map[string][]string) 134 // maps gating snap-ids to their snap names 135 gatingNames := make(map[string]string) 136 137 db := DB(s) 138 snapStates, err := snapstate.All(s) 139 if err != nil { 140 return nil, err 141 } 142 for instanceName, snapst := range snapStates { 143 info, err := snapst.CurrentInfo() 144 if err != nil { 145 return nil, err 146 } 147 if info.SnapID == "" { 148 continue 149 } 150 gatingID := info.SnapID 151 if gatingNames[gatingID] != "" { 152 continue 153 } 154 a, err := db.Find(asserts.SnapDeclarationType, map[string]string{ 155 "series": release.Series, 156 "snap-id": gatingID, 157 }) 158 if err != nil { 159 return nil, fmt.Errorf("internal error: cannot find snap declaration for installed snap %q: %v", instanceName, err) 160 } 161 decl := a.(*asserts.SnapDeclaration) 162 control := decl.RefreshControl() 163 if len(control) == 0 { 164 continue 165 } 166 gatingNames[gatingID] = decl.SnapName() 167 for _, gatedID := range control { 168 controlled[gatedID] = append(controlled[gatedID], gatingID) 169 } 170 } 171 172 var errs []error 173 for _, candInfo := range snapInfos { 174 if ignoreValidation[candInfo.InstanceName()] { 175 validated = append(validated, candInfo) 176 continue 177 } 178 gatedID := candInfo.SnapID 179 gating := controlled[gatedID] 180 if len(gating) == 0 { // easy case, no refresh control 181 validated = append(validated, candInfo) 182 continue 183 } 184 185 var validationRefs []*asserts.Ref 186 187 fetching := func(f asserts.Fetcher) error { 188 for _, gatingID := range gating { 189 valref := &asserts.Ref{ 190 Type: asserts.ValidationType, 191 PrimaryKey: []string{release.Series, gatingID, gatedID, candInfo.Revision.String()}, 192 } 193 err := f.Fetch(valref) 194 if notFound, ok := err.(*asserts.NotFoundError); ok && notFound.Type == asserts.ValidationType { 195 return fmt.Errorf("no validation by %q", gatingNames[gatingID]) 196 } 197 if err != nil { 198 return fmt.Errorf("cannot find validation by %q: %v", gatingNames[gatingID], err) 199 } 200 validationRefs = append(validationRefs, valref) 201 } 202 return nil 203 } 204 err := doFetch(s, userID, deviceCtx, fetching) 205 if err != nil { 206 errs = append(errs, fmt.Errorf("cannot refresh %q to revision %s: %v", candInfo.InstanceName(), candInfo.Revision, err)) 207 continue 208 } 209 210 var revoked *asserts.Validation 211 for _, valref := range validationRefs { 212 a, err := valref.Resolve(db.Find) 213 if err != nil { 214 return nil, findError("internal error: cannot find just fetched %v", valref, err) 215 } 216 if val := a.(*asserts.Validation); val.Revoked() { 217 revoked = val 218 break 219 } 220 } 221 if revoked != nil { 222 errs = append(errs, fmt.Errorf("cannot refresh %q to revision %s: validation by %q (id %q) revoked", candInfo.InstanceName(), candInfo.Revision, gatingNames[revoked.SnapID()], revoked.SnapID())) 223 continue 224 } 225 226 validated = append(validated, candInfo) 227 } 228 229 if errs != nil { 230 return validated, &refreshControlError{errs} 231 } 232 233 return validated, nil 234 } 235 236 // BaseDeclaration returns the base-declaration assertion with policies governing all snaps. 237 func BaseDeclaration(s *state.State) (*asserts.BaseDeclaration, error) { 238 // TODO: switch keeping this in the DB and have it revisioned/updated 239 // via the store 240 baseDecl := asserts.BuiltinBaseDeclaration() 241 if baseDecl == nil { 242 return nil, &asserts.NotFoundError{Type: asserts.BaseDeclarationType} 243 } 244 return baseDecl, nil 245 } 246 247 // SnapDeclaration returns the snap-declaration for the given snap-id if it is present in the system assertion database. 248 func SnapDeclaration(s *state.State, snapID string) (*asserts.SnapDeclaration, error) { 249 db := DB(s) 250 a, err := db.Find(asserts.SnapDeclarationType, map[string]string{ 251 "series": release.Series, 252 "snap-id": snapID, 253 }) 254 if err != nil { 255 return nil, err 256 } 257 return a.(*asserts.SnapDeclaration), nil 258 } 259 260 // Publisher returns the account assertion for publisher of the given snap-id if it is present in the system assertion database. 261 func Publisher(s *state.State, snapID string) (*asserts.Account, error) { 262 db := DB(s) 263 a, err := db.Find(asserts.SnapDeclarationType, map[string]string{ 264 "series": release.Series, 265 "snap-id": snapID, 266 }) 267 if err != nil { 268 return nil, err 269 } 270 snapDecl := a.(*asserts.SnapDeclaration) 271 a, err = db.Find(asserts.AccountType, map[string]string{ 272 "account-id": snapDecl.PublisherID(), 273 }) 274 if err != nil { 275 return nil, fmt.Errorf("internal error: cannot find account assertion for the publisher of snap %q: %v", snapDecl.SnapName(), err) 276 } 277 return a.(*asserts.Account), nil 278 } 279 280 // Store returns the store assertion with the given name/id if it is 281 // present in the system assertion database. 282 func Store(s *state.State, store string) (*asserts.Store, error) { 283 db := DB(s) 284 a, err := db.Find(asserts.StoreType, map[string]string{ 285 "store": store, 286 }) 287 if err != nil { 288 return nil, err 289 } 290 return a.(*asserts.Store), nil 291 } 292 293 // AutoAliases returns the explicit automatic aliases alias=>app mapping for the given installed snap. 294 func AutoAliases(s *state.State, info *snap.Info) (map[string]string, error) { 295 if info.SnapID == "" { 296 // without declaration 297 return nil, nil 298 } 299 decl, err := SnapDeclaration(s, info.SnapID) 300 if err != nil { 301 return nil, fmt.Errorf("internal error: cannot find snap-declaration for installed snap %q: %v", info.InstanceName(), err) 302 } 303 explicitAliases := decl.Aliases() 304 if len(explicitAliases) != 0 { 305 return explicitAliases, nil 306 } 307 // XXX: old header fallback, just to keep edge working while we fix the 308 // store, to remove before next release! 309 oldAutoAliases := decl.AutoAliases() 310 if len(oldAutoAliases) == 0 { 311 return nil, nil 312 } 313 res := make(map[string]string, len(oldAutoAliases)) 314 for _, alias := range oldAutoAliases { 315 app := info.LegacyAliases[alias] 316 if app == nil { 317 // not a known alias anymore or yet, skip 318 continue 319 320 } 321 res[alias] = app.Name 322 } 323 return res, nil 324 } 325 326 func delayedCrossMgrInit() { 327 // hook validation of refreshes into snapstate logic 328 snapstate.ValidateRefreshes = ValidateRefreshes 329 // hook auto refresh of assertions (snap declarations) into snapstate 330 snapstate.AutoRefreshAssertions = AutoRefreshAssertions 331 // hook retrieving auto-aliases into snapstate logic 332 snapstate.AutoAliases = AutoAliases 333 // hook the helper for getting enforced validation sets 334 snapstate.EnforcedValidationSets = EnforcedValidationSets 335 } 336 337 // AutoRefreshAssertions tries to refresh all assertions 338 func AutoRefreshAssertions(s *state.State, userID int) error { 339 if err := RefreshSnapDeclarations(s, userID); err != nil { 340 return err 341 } 342 return RefreshValidationSetAssertions(s, userID) 343 } 344 345 // RefreshValidationSetAssertions tries to refresh all validation set 346 // assertions. 347 func RefreshValidationSetAssertions(s *state.State, userID int) error { 348 deviceCtx, err := snapstate.DevicePastSeeding(s, nil) 349 if err != nil { 350 return err 351 } 352 353 vsets, err := ValidationSets(s) 354 if err != nil { 355 return err 356 } 357 if len(vsets) == 0 { 358 return nil 359 } 360 361 monitorModeSets := make(map[string]*ValidationSetTracking) 362 enforceModeSets := make(map[string]*ValidationSetTracking) 363 for vk, vset := range vsets { 364 if vset.Mode == Monitor { 365 monitorModeSets[vk] = vset 366 } else { 367 enforceModeSets[vk] = vset 368 } 369 } 370 371 updateTracking := func(sets map[string]*ValidationSetTracking) error { 372 // update validation set tracking state 373 for _, vs := range sets { 374 if vs.PinnedAt == 0 { 375 headers := map[string]string{ 376 "series": release.Series, 377 "account-id": vs.AccountID, 378 "name": vs.Name, 379 } 380 db := DB(s) 381 as, err := db.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 382 if err != nil { 383 return fmt.Errorf("internal error: cannot find assertion %v when refreshing validation-set assertions", headers) 384 } 385 if vs.Current != as.Sequence() { 386 vs.Current = as.Sequence() 387 UpdateValidationSet(s, vs) 388 } 389 } 390 } 391 return nil 392 } 393 394 if err := bulkRefreshValidationSetAsserts(s, monitorModeSets, nil, userID, deviceCtx); err != nil { 395 return err 396 } 397 if err := updateTracking(monitorModeSets); err != nil { 398 return err 399 } 400 401 checkForConflicts := func(db *asserts.Database, bs asserts.Backstore) error { 402 vsets := snapasserts.NewValidationSets() 403 tmpDb := db.WithStackedBackstore(bs) 404 for _, vs := range enforceModeSets { 405 headers := map[string]string{ 406 "series": release.Series, 407 "account-id": vs.AccountID, 408 "name": vs.Name, 409 } 410 var err error 411 var as asserts.Assertion 412 if vs.PinnedAt > 0 { 413 headers["sequence"] = fmt.Sprintf("%d", vs.PinnedAt) 414 as, err = tmpDb.Find(asserts.ValidationSetType, headers) 415 } else { 416 as, err = tmpDb.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 417 } 418 if err != nil { 419 return fmt.Errorf("internal error: cannot find validation set assertion: %v", err) 420 } 421 422 vsass, ok := as.(*asserts.ValidationSet) 423 if !ok { 424 return fmt.Errorf("internal error: unexpected assertion type %s for %s", vsass.Type().Name, ValidationSetKey(vs.AccountID, vs.Name)) 425 } 426 if err := vsets.Add(vsass); err != nil { 427 return fmt.Errorf("internal error: cannot check validation sets conflicts: %v", err) 428 } 429 } 430 return vsets.Conflict() 431 } 432 433 if err := bulkRefreshValidationSetAsserts(s, enforceModeSets, checkForConflicts, userID, deviceCtx); err != nil { 434 if _, ok := err.(*snapasserts.ValidationSetsConflictError); ok { 435 logger.Noticef("cannot refresh to conflicting validation set assertions: %v", err) 436 return nil 437 } 438 return err 439 } 440 if err := updateTracking(enforceModeSets); err != nil { 441 return err 442 } 443 444 return nil 445 } 446 447 // ResolveOptions carries extra options for ValidationSetAssertionForMonitor. 448 type ResolveOptions struct { 449 AllowLocalFallback bool 450 } 451 452 // ValidationSetAssertionForMonitor tries to fetch or refresh the validation 453 // set assertion with accountID/name/sequence (sequence is optional) using pool. 454 // If assertion cannot be fetched but exists locally and opts.AllowLocalFallback 455 // is set then the local one is returned 456 func ValidationSetAssertionForMonitor(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *ResolveOptions) (as *asserts.ValidationSet, local bool, err error) { 457 if opts == nil { 458 opts = &ResolveOptions{} 459 } 460 deviceCtx, err := snapstate.DevicePastSeeding(st, nil) 461 if err != nil { 462 return nil, false, err 463 } 464 465 var vs asserts.Assertion 466 headers := map[string]string{ 467 "series": release.Series, 468 "account-id": accountID, 469 "name": name, 470 } 471 472 db := cachedDB(st) 473 474 // try to get existing one from db 475 if sequence > 0 { 476 headers["sequence"] = fmt.Sprintf("%d", sequence) 477 vs, err = db.Find(asserts.ValidationSetType, headers) 478 } else { 479 // find latest 480 vs, err = db.FindSequence(asserts.ValidationSetType, headers, -1, -1) 481 } 482 if err != nil && !asserts.IsNotFound(err) { 483 return nil, false, err 484 } 485 if err == nil { 486 as = vs.(*asserts.ValidationSet) 487 } 488 489 // try to resolve or update with pool 490 pool := asserts.NewPool(db, maxGroups) 491 atSeq := &asserts.AtSequence{ 492 Type: asserts.ValidationSetType, 493 SequenceKey: []string{release.Series, accountID, name}, 494 Sequence: sequence, 495 Pinned: pinned, 496 } 497 if as != nil { 498 atSeq.Revision = as.Revision() 499 } else { 500 atSeq.Revision = asserts.RevisionNotKnown 501 } 502 503 // resolve if not found locally, otherwise add for update 504 if as == nil { 505 if err := pool.AddUnresolvedSequence(atSeq, atSeq.Unique()); err != nil { 506 return nil, false, err 507 } 508 } else { 509 atSeq.Sequence = as.Sequence() 510 // found locally, try to update 511 atSeq.Revision = as.Revision() 512 if err := pool.AddSequenceToUpdate(atSeq, atSeq.Unique()); err != nil { 513 return nil, false, err 514 } 515 } 516 517 if err := resolvePoolNoFallback(st, pool, nil, userID, deviceCtx); err != nil { 518 rerr, ok := err.(*resolvePoolError) 519 if ok && as != nil && opts.AllowLocalFallback { 520 if e := rerr.errors[atSeq.Unique()]; asserts.IsNotFound(e) { 521 // fallback: support the scenario of local assertion (snap ack) 522 // not available in the store. 523 return as, true, nil 524 } 525 } 526 return nil, false, err 527 } 528 529 // fetch the requested assertion again 530 if pinned { 531 vs, err = db.Find(asserts.ValidationSetType, headers) 532 } else { 533 vs, err = db.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 534 } 535 if err == nil { 536 as = vs.(*asserts.ValidationSet) 537 } 538 return as, false, err 539 } 540 541 // ValidationSetAssertionForEnforce tries to fetch the validation set assertion 542 // with the given accountID/name/sequence (sequence is optional) using pool and 543 // checks if it's not in conflict with existing validation sets in enforcing mode 544 // (all currently tracked validation set assertions get refreshed), and if they 545 // are valid for installed snaps. 546 func ValidationSetAssertionForEnforce(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap) (vs *asserts.ValidationSet, err error) { 547 deviceCtx, err := snapstate.DevicePastSeeding(st, nil) 548 if err != nil { 549 return nil, err 550 } 551 552 // refresh all currently tracked validation set assertions (this may or may not 553 // include the one requested by the caller). 554 if err = RefreshValidationSetAssertions(st, userID); err != nil { 555 return nil, err 556 } 557 558 valsets, err := EnforcedValidationSets(st) 559 if err != nil { 560 return nil, err 561 } 562 563 getSpecificSequenceOrLatest := func(db *asserts.Database, headers map[string]string) (vs *asserts.ValidationSet, err error) { 564 var a asserts.Assertion 565 if _, ok := headers["sequence"]; ok { 566 a, err = db.Find(asserts.ValidationSetType, headers) 567 } else { 568 a, err = db.FindSequence(asserts.ValidationSetType, headers, -1, -1) 569 } 570 if err != nil { 571 return nil, err 572 } 573 vs = a.(*asserts.ValidationSet) 574 return vs, nil 575 } 576 577 // try to get existing from the db. It will be the latest one if it was 578 // tracked already and thus refreshed via RefreshValidationSetAssertions. 579 // Otherwise, it may be a local assertion that was tracked in the past and 580 // then forgotten, in which case we need to refresh it explicitly. 581 db := cachedDB(st) 582 headers := map[string]string{ 583 "series": release.Series, 584 "account-id": accountID, 585 "name": name, 586 } 587 if sequence > 0 { 588 headers["sequence"] = fmt.Sprintf("%d", sequence) 589 } 590 591 pool := asserts.NewPool(db, maxGroups) 592 atSeq := &asserts.AtSequence{ 593 Type: asserts.ValidationSetType, 594 SequenceKey: []string{release.Series, accountID, name}, 595 Sequence: sequence, 596 Revision: asserts.RevisionNotKnown, 597 Pinned: sequence > 0, 598 } 599 600 vs, err = getSpecificSequenceOrLatest(db, headers) 601 602 checkForConflicts := func() error { 603 if err := valsets.Add(vs); err != nil { 604 return fmt.Errorf("internal error: cannot check validation sets conflicts: %v", err) 605 } 606 if err := valsets.Conflict(); err != nil { 607 return err 608 } 609 if err := valsets.CheckInstalledSnaps(snaps); err != nil { 610 return err 611 } 612 return nil 613 } 614 615 // found locally 616 if err == nil { 617 // check if we were tracking it already; if not, that 618 // means we found an old assertion (it was very likely tracked in the 619 // past) and we need to update it as it wasn't covered 620 // by RefreshValidationSetAssertions. 621 var tr ValidationSetTracking 622 trerr := GetValidationSet(st, accountID, name, &tr) 623 if trerr != nil && trerr != state.ErrNoState { 624 return nil, trerr 625 } 626 // not tracked, update the assertion 627 if trerr == state.ErrNoState { 628 // update with pool 629 atSeq.Sequence = vs.Sequence() 630 atSeq.Revision = vs.Revision() 631 if err := pool.AddSequenceToUpdate(atSeq, atSeq.Unique()); err != nil { 632 return nil, err 633 } 634 } else { 635 // was already tracked, add to validation sets and check 636 if err := checkForConflicts(); err != nil { 637 return nil, err 638 } 639 return vs, nil 640 } 641 } else { 642 if !asserts.IsNotFound(err) { 643 return nil, err 644 } 645 646 // try to resolve with pool 647 if err := pool.AddUnresolvedSequence(atSeq, atSeq.Unique()); err != nil { 648 return nil, err 649 } 650 } 651 652 checkBeforeCommit := func(db *asserts.Database, bs asserts.Backstore) error { 653 tmpDb := db.WithStackedBackstore(bs) 654 // get the resolved validation set assert, add to validation sets and check 655 vs, err = getSpecificSequenceOrLatest(tmpDb, headers) 656 if err != nil { 657 return fmt.Errorf("internal error: cannot find validation set assertion: %v", err) 658 } 659 if err := checkForConflicts(); err != nil { 660 return err 661 } 662 // all fine, will be committed (along with its prerequisites if any) on 663 // return by resolvePoolNoFallback 664 return nil 665 } 666 667 if err := resolvePoolNoFallback(st, pool, checkBeforeCommit, userID, deviceCtx); err != nil { 668 return nil, err 669 } 670 671 return vs, err 672 } 673 674 // TemporaryDB returns a temporary database stacked on top of the assertions 675 // database. Writing to it will not affect the assertions database. 676 func TemporaryDB(st *state.State) *asserts.Database { 677 db := cachedDB(st) 678 return db.WithStackedBackstore(asserts.NewMemoryBackstore()) 679 }