github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 } 334 335 // AutoRefreshAssertions tries to refresh all assertions 336 func AutoRefreshAssertions(s *state.State, userID int) error { 337 if err := RefreshSnapDeclarations(s, userID); err != nil { 338 return err 339 } 340 return RefreshValidationSetAssertions(s, userID) 341 } 342 343 // RefreshValidationSetAssertions tries to refresh all validation set 344 // assertions. 345 func RefreshValidationSetAssertions(s *state.State, userID int) error { 346 deviceCtx, err := snapstate.DevicePastSeeding(s, nil) 347 if err != nil { 348 return err 349 } 350 351 vsets, err := ValidationSets(s) 352 if err != nil { 353 return err 354 } 355 if len(vsets) == 0 { 356 return nil 357 } 358 359 monitorModeSets := make(map[string]*ValidationSetTracking) 360 enforceModeSets := make(map[string]*ValidationSetTracking) 361 for vk, vset := range vsets { 362 if vset.Mode == Monitor { 363 monitorModeSets[vk] = vset 364 } else { 365 enforceModeSets[vk] = vset 366 } 367 } 368 369 updateTracking := func(sets map[string]*ValidationSetTracking) error { 370 // update validation set tracking state 371 for _, vs := range sets { 372 if vs.PinnedAt == 0 { 373 headers := map[string]string{ 374 "series": release.Series, 375 "account-id": vs.AccountID, 376 "name": vs.Name, 377 } 378 db := DB(s) 379 as, err := db.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 380 if err != nil { 381 return fmt.Errorf("internal error: cannot find assertion %v when refreshing validation-set assertions", headers) 382 } 383 if vs.Current != as.Sequence() { 384 vs.Current = as.Sequence() 385 UpdateValidationSet(s, vs) 386 } 387 } 388 } 389 return nil 390 } 391 392 if err := bulkRefreshValidationSetAsserts(s, monitorModeSets, nil, userID, deviceCtx); err != nil { 393 return err 394 } 395 if err := updateTracking(monitorModeSets); err != nil { 396 return err 397 } 398 399 checkForConflicts := func(db *asserts.Database, bs asserts.Backstore) error { 400 vsets := snapasserts.NewValidationSets() 401 tmpDb := db.WithStackedBackstore(bs) 402 for _, vs := range enforceModeSets { 403 headers := map[string]string{ 404 "series": release.Series, 405 "account-id": vs.AccountID, 406 "name": vs.Name, 407 } 408 var err error 409 var as asserts.Assertion 410 if vs.PinnedAt > 0 { 411 headers["sequence"] = fmt.Sprintf("%d", vs.PinnedAt) 412 as, err = tmpDb.Find(asserts.ValidationSetType, headers) 413 } else { 414 as, err = tmpDb.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 415 } 416 if err != nil { 417 return fmt.Errorf("internal error: cannot find validation set assertion: %v", err) 418 } 419 420 vsass, ok := as.(*asserts.ValidationSet) 421 if !ok { 422 return fmt.Errorf("internal error: unexpected assertion type %s for %s", vsass.Type().Name, ValidationSetKey(vs.AccountID, vs.Name)) 423 } 424 if err := vsets.Add(vsass); err != nil { 425 return fmt.Errorf("internal error: cannot check validation sets conflicts: %v", err) 426 } 427 } 428 return vsets.Conflict() 429 } 430 431 if err := bulkRefreshValidationSetAsserts(s, enforceModeSets, checkForConflicts, userID, deviceCtx); err != nil { 432 if _, ok := err.(*snapasserts.ValidationSetsConflictError); ok { 433 logger.Noticef("cannot refresh to conflicting validation set assertions: %v", err) 434 return nil 435 } 436 return err 437 } 438 if err := updateTracking(enforceModeSets); err != nil { 439 return err 440 } 441 442 return nil 443 } 444 445 // ResolveOptions carries extra options for ValidationSetAssertionForMonitor. 446 type ResolveOptions struct { 447 AllowLocalFallback bool 448 } 449 450 // ValidationSetAssertionForMonitor tries to fetch or refresh the validation 451 // set assertion with accountID/name/sequence (sequence is optional) using pool. 452 // If assertion cannot be fetched but exists locally and opts.AllowLocalFallback 453 // is set then the local one is returned 454 func ValidationSetAssertionForMonitor(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *ResolveOptions) (as *asserts.ValidationSet, local bool, err error) { 455 if opts == nil { 456 opts = &ResolveOptions{} 457 } 458 deviceCtx, err := snapstate.DevicePastSeeding(st, nil) 459 if err != nil { 460 return nil, false, err 461 } 462 463 var vs asserts.Assertion 464 headers := map[string]string{ 465 "series": release.Series, 466 "account-id": accountID, 467 "name": name, 468 } 469 470 db := cachedDB(st) 471 472 // try to get existing one from db 473 if sequence > 0 { 474 headers["sequence"] = fmt.Sprintf("%d", sequence) 475 vs, err = db.Find(asserts.ValidationSetType, headers) 476 } else { 477 // find latest 478 vs, err = db.FindSequence(asserts.ValidationSetType, headers, -1, -1) 479 } 480 if err != nil && !asserts.IsNotFound(err) { 481 return nil, false, err 482 } 483 if err == nil { 484 as = vs.(*asserts.ValidationSet) 485 } 486 487 // try to resolve or update with pool 488 pool := asserts.NewPool(db, maxGroups) 489 atSeq := &asserts.AtSequence{ 490 Type: asserts.ValidationSetType, 491 SequenceKey: []string{release.Series, accountID, name}, 492 Sequence: sequence, 493 Pinned: pinned, 494 } 495 if as != nil { 496 atSeq.Revision = as.Revision() 497 } else { 498 atSeq.Revision = asserts.RevisionNotKnown 499 } 500 501 // resolve if not found locally, otherwise add for update 502 if as == nil { 503 if err := pool.AddUnresolvedSequence(atSeq, atSeq.Unique()); err != nil { 504 return nil, false, err 505 } 506 } else { 507 atSeq.Sequence = as.Sequence() 508 // found locally, try to update 509 atSeq.Revision = as.Revision() 510 if err := pool.AddSequenceToUpdate(atSeq, atSeq.Unique()); err != nil { 511 return nil, false, err 512 } 513 } 514 515 if err := resolvePoolNoFallback(st, pool, nil, userID, deviceCtx); err != nil { 516 rerr, ok := err.(*resolvePoolError) 517 if ok && as != nil && opts.AllowLocalFallback { 518 if e := rerr.errors[atSeq.Unique()]; asserts.IsNotFound(e) { 519 // fallback: support the scenario of local assertion (snap ack) 520 // not available in the store. 521 return as, true, nil 522 } 523 } 524 return nil, false, err 525 } 526 527 // fetch the requested assertion again 528 if pinned { 529 vs, err = db.Find(asserts.ValidationSetType, headers) 530 } else { 531 vs, err = db.FindSequence(asserts.ValidationSetType, headers, -1, asserts.ValidationSetType.MaxSupportedFormat()) 532 } 533 if err == nil { 534 as = vs.(*asserts.ValidationSet) 535 } 536 return as, false, err 537 } 538 539 // TemporaryDB returns a temporary database stacked on top of the assertions 540 // database. Writing to it will not affect the assertions database. 541 func TemporaryDB(st *state.State) *asserts.Database { 542 db := cachedDB(st) 543 return db.WithStackedBackstore(asserts.NewMemoryBackstore()) 544 }