gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/snapasserts/validation_sets.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 snapasserts 21 22 import ( 23 "bytes" 24 "fmt" 25 "sort" 26 "strconv" 27 "strings" 28 29 "gitee.com/mysnapcore/mysnapd/asserts" 30 "gitee.com/mysnapcore/mysnapd/snap" 31 "gitee.com/mysnapcore/mysnapd/snap/naming" 32 ) 33 34 // InstalledSnap holds the minimal details about an installed snap required to 35 // check it against validation sets. 36 type InstalledSnap struct { 37 naming.SnapRef 38 Revision snap.Revision 39 } 40 41 // NewInstalledSnap creates InstalledSnap. 42 func NewInstalledSnap(name, snapID string, revision snap.Revision) *InstalledSnap { 43 return &InstalledSnap{ 44 SnapRef: naming.NewSnapRef(name, snapID), 45 Revision: revision, 46 } 47 } 48 49 // ValidationSetsConflictError describes an error where multiple 50 // validation sets are in conflict about snaps. 51 type ValidationSetsConflictError struct { 52 Sets map[string]*asserts.ValidationSet 53 Snaps map[string]error 54 } 55 56 func (e *ValidationSetsConflictError) Error() string { 57 buf := bytes.NewBufferString("validation sets are in conflict:") 58 for _, err := range e.Snaps { 59 fmt.Fprintf(buf, "\n- %v", err) 60 } 61 return buf.String() 62 } 63 64 // ValidationSetsValidationError describes an error arising 65 // from validation of snaps against ValidationSets. 66 type ValidationSetsValidationError struct { 67 // MissingSnaps maps missing snap names to the expected revisions and respective validation sets requiring them. 68 // Revisions may be unset if no specific revision is required 69 MissingSnaps map[string]map[snap.Revision][]string 70 // InvalidSnaps maps snap names to the validation sets declaring them invalid. 71 InvalidSnaps map[string][]string 72 // WronRevisionSnaps maps snap names to the expected revisions and respective 73 // validation sets that require them. 74 WrongRevisionSnaps map[string]map[snap.Revision][]string 75 // Sets maps validation set keys to all validation sets assertions considered 76 // in the failed check. 77 Sets map[string]*asserts.ValidationSet 78 } 79 80 // ValidationSetKey is a string-backed primary key for a validation set assertion. 81 type ValidationSetKey string 82 83 // NewValidationSetKey returns a validation set key for a validation set. 84 func NewValidationSetKey(vs *asserts.ValidationSet) ValidationSetKey { 85 return ValidationSetKey(strings.Join(vs.Ref().PrimaryKey, "/")) 86 } 87 88 func (k ValidationSetKey) String() string { 89 return string(k) 90 } 91 92 // Components returns the components of the validation set's primary key (see 93 // assertion types in asserts/asserts.go). 94 func (k ValidationSetKey) Components() []string { 95 return strings.Split(k.String(), "/") 96 } 97 98 // ValidationSetKeySlice can be used to sort slices of ValidationSetKey. 99 type ValidationSetKeySlice []ValidationSetKey 100 101 func (s ValidationSetKeySlice) Len() int { return len(s) } 102 func (s ValidationSetKeySlice) Less(i, j int) bool { return s[i] < s[j] } 103 func (s ValidationSetKeySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 104 105 // CommaSeparated returns the validation set keys separated by commas. 106 func (s ValidationSetKeySlice) CommaSeparated() string { 107 var sb strings.Builder 108 109 for i, vsKey := range s { 110 sb.WriteString(vsKey.String()) 111 if i < len(s)-1 { 112 sb.WriteRune(',') 113 } 114 } 115 116 return sb.String() 117 } 118 119 type byRevision []snap.Revision 120 121 func (b byRevision) Len() int { return len(b) } 122 func (b byRevision) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 123 func (b byRevision) Less(i, j int) bool { return b[i].N < b[j].N } 124 125 func (e *ValidationSetsValidationError) Error() string { 126 buf := bytes.NewBufferString("validation sets assertions are not met:") 127 printDetails := func(header string, details map[string][]string, 128 printSnap func(snapName string, keys []string) string) { 129 if len(details) == 0 { 130 return 131 } 132 fmt.Fprintf(buf, "\n- %s:", header) 133 for snapName, validationSetKeys := range details { 134 fmt.Fprintf(buf, "\n - %s", printSnap(snapName, validationSetKeys)) 135 } 136 } 137 138 if len(e.MissingSnaps) > 0 { 139 fmt.Fprintf(buf, "\n- missing required snaps:") 140 for snapName, revisions := range e.MissingSnaps { 141 revisionsSorted := make([]snap.Revision, 0, len(revisions)) 142 for rev := range revisions { 143 revisionsSorted = append(revisionsSorted, rev) 144 } 145 sort.Sort(byRevision(revisionsSorted)) 146 t := make([]string, 0, len(revisionsSorted)) 147 for _, rev := range revisionsSorted { 148 keys := revisions[rev] 149 if rev.Unset() { 150 t = append(t, fmt.Sprintf("at any revision by sets %s", strings.Join(keys, ","))) 151 } else { 152 t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ","))) 153 } 154 } 155 fmt.Fprintf(buf, "\n - %s (required %s)", snapName, strings.Join(t, ", ")) 156 } 157 } 158 159 printDetails("invalid snaps", e.InvalidSnaps, func(snapName string, validationSetKeys []string) string { 160 return fmt.Sprintf("%s (invalid for sets %s)", snapName, strings.Join(validationSetKeys, ",")) 161 }) 162 163 if len(e.WrongRevisionSnaps) > 0 { 164 fmt.Fprint(buf, "\n- snaps at wrong revisions:") 165 for snapName, revisions := range e.WrongRevisionSnaps { 166 revisionsSorted := make([]snap.Revision, 0, len(revisions)) 167 for rev := range revisions { 168 revisionsSorted = append(revisionsSorted, rev) 169 } 170 sort.Sort(byRevision(revisionsSorted)) 171 t := make([]string, 0, len(revisionsSorted)) 172 for _, rev := range revisionsSorted { 173 keys := revisions[rev] 174 t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ","))) 175 } 176 fmt.Fprintf(buf, "\n - %s (required %s)", snapName, strings.Join(t, ", ")) 177 } 178 } 179 180 return buf.String() 181 } 182 183 // ValidationSets can hold a combination of validation-set assertions 184 // and can check for conflicts or help applying them. 185 type ValidationSets struct { 186 // sets maps sequence keys to validation-set in the combination 187 sets map[string]*asserts.ValidationSet 188 // snaps maps snap-ids to snap constraints 189 snaps map[string]*snapContraints 190 } 191 192 const presConflict asserts.Presence = "conflict" 193 194 var unspecifiedRevision = snap.R(0) 195 var invalidPresRevision = snap.R(-1) 196 197 type snapContraints struct { 198 name string 199 presence asserts.Presence 200 // revisions maps revisions to pairing of ValidationSetSnap 201 // and the originating validation-set key 202 // * unspecifiedRevision is used for constraints without a 203 // revision 204 // * invalidPresRevision is used for constraints that mark 205 // presence as invalid 206 revisions map[snap.Revision][]*revConstraint 207 } 208 209 type revConstraint struct { 210 validationSetKey string 211 asserts.ValidationSetSnap 212 } 213 214 func (c *snapContraints) conflict() *snapConflictsError { 215 if c.presence != presConflict { 216 return nil 217 } 218 219 const dontCare asserts.Presence = "" 220 whichSets := func(rcs []*revConstraint, presence asserts.Presence) []string { 221 which := make([]string, 0, len(rcs)) 222 for _, rc := range rcs { 223 if presence != dontCare && rc.Presence != presence { 224 continue 225 } 226 which = append(which, rc.validationSetKey) 227 } 228 if len(which) == 0 { 229 return nil 230 } 231 sort.Strings(which) 232 return which 233 } 234 235 byRev := make(map[snap.Revision][]string, len(c.revisions)) 236 for r := range c.revisions { 237 pres := dontCare 238 switch r { 239 case invalidPresRevision: 240 pres = asserts.PresenceInvalid 241 case unspecifiedRevision: 242 pres = asserts.PresenceRequired 243 } 244 which := whichSets(c.revisions[r], pres) 245 if len(which) != 0 { 246 byRev[r] = which 247 } 248 } 249 250 return &snapConflictsError{ 251 name: c.name, 252 revisions: byRev, 253 } 254 } 255 256 type snapConflictsError struct { 257 name string 258 // revisions maps revisions to validation-set keys of the sets 259 // that are in conflict over the revision. 260 // * unspecifiedRevision is used for validation-sets conflicting 261 // on the snap by requiring it but without a revision 262 // * invalidPresRevision is used for validation-sets that mark 263 // presence as invalid 264 // see snapContraints.revisions as well 265 revisions map[snap.Revision][]string 266 } 267 268 func (e *snapConflictsError) Error() string { 269 whichSets := func(which []string) string { 270 return fmt.Sprintf("(%s)", strings.Join(which, ",")) 271 } 272 273 msg := fmt.Sprintf("cannot constrain snap %q", e.name) 274 invalid := false 275 if invalidOnes, ok := e.revisions[invalidPresRevision]; ok { 276 msg += fmt.Sprintf(" as both invalid %s and required", whichSets(invalidOnes)) 277 invalid = true 278 } 279 280 var revnos []snap.Revision 281 for r := range e.revisions { 282 if r.N >= 1 { 283 revnos = append(revnos, r) 284 } 285 } 286 if len(revnos) == 1 { 287 msg += fmt.Sprintf(" at revision %s %s", revnos[0], whichSets(e.revisions[revnos[0]])) 288 } else if len(revnos) > 1 { 289 sort.Sort(byRevision(revnos)) 290 l := make([]string, 0, len(revnos)) 291 for _, rev := range revnos { 292 l = append(l, fmt.Sprintf("%s %s", rev, whichSets(e.revisions[rev]))) 293 } 294 msg += fmt.Sprintf(" at different revisions %s", strings.Join(l, ", ")) 295 } 296 297 if unspecifiedOnes, ok := e.revisions[unspecifiedRevision]; ok { 298 which := whichSets(unspecifiedOnes) 299 if which != "" { 300 if len(revnos) != 0 { 301 msg += " or" 302 } 303 if invalid { 304 msg += fmt.Sprintf(" at any revision %s", which) 305 } else { 306 msg += fmt.Sprintf(" required at any revision %s", which) 307 } 308 } 309 } 310 return msg 311 } 312 313 // NewValidationSets returns a new ValidationSets. 314 func NewValidationSets() *ValidationSets { 315 return &ValidationSets{ 316 sets: map[string]*asserts.ValidationSet{}, 317 snaps: map[string]*snapContraints{}, 318 } 319 } 320 321 func valSetKey(valset *asserts.ValidationSet) string { 322 return fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name()) 323 } 324 325 // Add adds the given asserts.ValidationSet to the combination. 326 // It errors if a validation-set with the same sequence key has been 327 // added already. 328 func (v *ValidationSets) Add(valset *asserts.ValidationSet) error { 329 k := valSetKey(valset) 330 if _, ok := v.sets[k]; ok { 331 return fmt.Errorf("cannot add a second validation-set under %q", k) 332 } 333 v.sets[k] = valset 334 for _, sn := range valset.Snaps() { 335 v.addSnap(sn, k) 336 } 337 return nil 338 } 339 340 func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey string) { 341 rev := snap.R(sn.Revision) 342 if sn.Presence == asserts.PresenceInvalid { 343 rev = invalidPresRevision 344 } 345 346 rc := &revConstraint{ 347 validationSetKey: validationSetKey, 348 ValidationSetSnap: *sn, 349 } 350 351 cs := v.snaps[sn.SnapID] 352 if cs == nil { 353 v.snaps[sn.SnapID] = &snapContraints{ 354 name: sn.Name, 355 presence: sn.Presence, 356 revisions: map[snap.Revision][]*revConstraint{ 357 rev: {rc}, 358 }, 359 } 360 return 361 } 362 363 cs.revisions[rev] = append(cs.revisions[rev], rc) 364 if cs.presence == presConflict { 365 // nothing to check anymore 366 return 367 } 368 // this counts really different revisions or invalid 369 ndiff := len(cs.revisions) 370 if _, ok := cs.revisions[unspecifiedRevision]; ok { 371 ndiff-- 372 } 373 switch { 374 case cs.presence == asserts.PresenceOptional: 375 cs.presence = sn.Presence 376 fallthrough 377 case cs.presence == sn.Presence || sn.Presence == asserts.PresenceOptional: 378 if ndiff > 1 { 379 if cs.presence == asserts.PresenceRequired { 380 // different revisions required/invalid 381 cs.presence = presConflict 382 return 383 } 384 // multiple optional at different revisions => invalid 385 cs.presence = asserts.PresenceInvalid 386 } 387 return 388 } 389 // we are left with a combo of required and invalid => conflict 390 cs.presence = presConflict 391 } 392 393 // Conflict returns a non-nil error if the combination is in conflict, 394 // nil otherwise. 395 func (v *ValidationSets) Conflict() error { 396 sets := make(map[string]*asserts.ValidationSet) 397 snaps := make(map[string]error) 398 399 for snapID, snConstrs := range v.snaps { 400 snConflictsErr := snConstrs.conflict() 401 if snConflictsErr != nil { 402 snaps[snapID] = snConflictsErr 403 for _, valsetKeys := range snConflictsErr.revisions { 404 for _, valsetKey := range valsetKeys { 405 sets[valsetKey] = v.sets[valsetKey] 406 } 407 } 408 } 409 } 410 411 if len(snaps) != 0 { 412 return &ValidationSetsConflictError{ 413 Sets: sets, 414 Snaps: snaps, 415 } 416 } 417 return nil 418 } 419 420 // CheckInstalledSnaps checks installed snaps against the validation sets. 421 func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap, ignoreValidation map[string]bool) error { 422 installed := naming.NewSnapSet(nil) 423 for _, sn := range snaps { 424 installed.Add(sn) 425 } 426 427 // snapName -> validationSet key -> validation set 428 invalid := make(map[string]map[string]bool) 429 missing := make(map[string]map[snap.Revision]map[string]bool) 430 wrongrev := make(map[string]map[snap.Revision]map[string]bool) 431 432 for _, cstrs := range v.snaps { 433 for rev, revCstr := range cstrs.revisions { 434 for _, rc := range revCstr { 435 sn := installed.Lookup(rc) 436 isInstalled := sn != nil 437 438 if isInstalled && ignoreValidation[rc.Name] { 439 continue 440 } 441 442 switch { 443 case !isInstalled && (cstrs.presence == asserts.PresenceOptional || cstrs.presence == asserts.PresenceInvalid): 444 // not installed, but optional or not required 445 case isInstalled && cstrs.presence == asserts.PresenceInvalid: 446 // installed but not expected to be present 447 if invalid[rc.Name] == nil { 448 invalid[rc.Name] = make(map[string]bool) 449 } 450 invalid[rc.Name][rc.validationSetKey] = true 451 case isInstalled: 452 // presence is either optional or required 453 if rev != unspecifiedRevision && rev != sn.(*InstalledSnap).Revision { 454 // expected a different revision 455 if wrongrev[rc.Name] == nil { 456 wrongrev[rc.Name] = make(map[snap.Revision]map[string]bool) 457 } 458 if wrongrev[rc.Name][rev] == nil { 459 wrongrev[rc.Name][rev] = make(map[string]bool) 460 } 461 wrongrev[rc.Name][rev][rc.validationSetKey] = true 462 } 463 default: 464 // not installed but required. 465 // note, not checking ignoreValidation here because it's not a viable scenario (it's not 466 // possible to have enforced validation set while not having the required snap at all - it 467 // is only possible to have it with a wrong revision, or installed while invalid, in both 468 // cases through --ignore-validation flag). 469 if missing[rc.Name] == nil { 470 missing[rc.Name] = make(map[snap.Revision]map[string]bool) 471 } 472 if missing[rc.Name][rev] == nil { 473 missing[rc.Name][rev] = make(map[string]bool) 474 } 475 missing[rc.Name][rev][rc.validationSetKey] = true 476 } 477 } 478 } 479 } 480 481 setsToLists := func(in map[string]map[string]bool) map[string][]string { 482 if len(in) == 0 { 483 return nil 484 } 485 out := make(map[string][]string) 486 for snap, sets := range in { 487 out[snap] = make([]string, 0, len(sets)) 488 for validationSetKey := range sets { 489 out[snap] = append(out[snap], validationSetKey) 490 } 491 sort.Strings(out[snap]) 492 } 493 return out 494 } 495 496 if len(invalid) > 0 || len(missing) > 0 || len(wrongrev) > 0 { 497 verr := &ValidationSetsValidationError{ 498 InvalidSnaps: setsToLists(invalid), 499 Sets: v.sets, 500 } 501 if len(missing) > 0 { 502 verr.MissingSnaps = make(map[string]map[snap.Revision][]string) 503 for snapName, revs := range missing { 504 verr.MissingSnaps[snapName] = make(map[snap.Revision][]string) 505 for rev, keys := range revs { 506 for key := range keys { 507 verr.MissingSnaps[snapName][rev] = append(verr.MissingSnaps[snapName][rev], key) 508 } 509 sort.Strings(verr.MissingSnaps[snapName][rev]) 510 } 511 } 512 } 513 if len(wrongrev) > 0 { 514 verr.WrongRevisionSnaps = make(map[string]map[snap.Revision][]string) 515 for snapName, revs := range wrongrev { 516 verr.WrongRevisionSnaps[snapName] = make(map[snap.Revision][]string) 517 for rev, keys := range revs { 518 for key := range keys { 519 verr.WrongRevisionSnaps[snapName][rev] = append(verr.WrongRevisionSnaps[snapName][rev], key) 520 } 521 sort.Strings(verr.WrongRevisionSnaps[snapName][rev]) 522 } 523 } 524 } 525 return verr 526 } 527 return nil 528 } 529 530 // PresenceConstraintError describes an error where presence of the given snap 531 // has unexpected value, e.g. it's "invalid" while checking for "required". 532 type PresenceConstraintError struct { 533 SnapName string 534 Presence asserts.Presence 535 } 536 537 func (e *PresenceConstraintError) Error() string { 538 return fmt.Sprintf("unexpected presence %q for snap %q", e.Presence, e.SnapName) 539 } 540 541 func (v *ValidationSets) constraintsForSnap(snapRef naming.SnapRef) *snapContraints { 542 if snapRef.ID() != "" { 543 return v.snaps[snapRef.ID()] 544 } 545 // snapID not available, find by snap name 546 for _, cstrs := range v.snaps { 547 if cstrs.name == snapRef.SnapName() { 548 return cstrs 549 } 550 } 551 return nil 552 } 553 554 // CheckPresenceRequired returns the list of all validation sets that declare 555 // presence of the given snap as required and the required revision (or 556 // snap.R(0) if no specific revision is required). PresenceConstraintError is 557 // returned if presence of the snap is "invalid". 558 // The method assumes that validation sets are not in conflict. 559 func (v *ValidationSets) CheckPresenceRequired(snapRef naming.SnapRef) ([]ValidationSetKey, snap.Revision, error) { 560 cstrs := v.constraintsForSnap(snapRef) 561 if cstrs == nil { 562 return nil, unspecifiedRevision, nil 563 } 564 if cstrs.presence == asserts.PresenceInvalid { 565 return nil, unspecifiedRevision, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} 566 } 567 if cstrs.presence != asserts.PresenceRequired { 568 return nil, unspecifiedRevision, nil 569 } 570 571 snapRev := unspecifiedRevision 572 var keys []ValidationSetKey 573 for rev, revCstr := range cstrs.revisions { 574 for _, rc := range revCstr { 575 vs := v.sets[rc.validationSetKey] 576 if vs == nil { 577 return nil, unspecifiedRevision, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey) 578 } 579 keys = append(keys, NewValidationSetKey(vs)) 580 // there may be constraints without revision; only set snapRev if 581 // it wasn't already determined. Note that if revisions are set, 582 // then they are the same, otherwise validation sets would be in 583 // conflict. 584 // This is an equivalent of 'if rev != unspecifiedRevision`. 585 if snapRev == unspecifiedRevision { 586 snapRev = rev 587 } 588 } 589 } 590 591 sort.Sort(ValidationSetKeySlice(keys)) 592 return keys, snapRev, nil 593 } 594 595 // CheckPresenceInvalid returns the list of all validation sets that declare 596 // presence of the given snap as invalid. PresenceConstraintError is returned if 597 // presence of the snap is "optional" or "required". 598 // The method assumes that validation sets are not in conflict. 599 func (v *ValidationSets) CheckPresenceInvalid(snapRef naming.SnapRef) ([]ValidationSetKey, error) { 600 cstrs := v.constraintsForSnap(snapRef) 601 if cstrs == nil { 602 return nil, nil 603 } 604 if cstrs.presence != asserts.PresenceInvalid { 605 return nil, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} 606 } 607 var keys []ValidationSetKey 608 for _, revCstr := range cstrs.revisions { 609 for _, rc := range revCstr { 610 if rc.Presence == asserts.PresenceInvalid { 611 vs := v.sets[rc.validationSetKey] 612 if vs == nil { 613 return nil, fmt.Errorf("internal error: no validation set for %q", rc.validationSetKey) 614 } 615 keys = append(keys, NewValidationSetKey(vs)) 616 } 617 } 618 } 619 620 sort.Sort(ValidationSetKeySlice(keys)) 621 return keys, nil 622 } 623 624 // ParseValidationSet parses a validation set string (account/name or account/name=sequence) 625 // and returns its individual components, or an error. 626 func ParseValidationSet(arg string) (account, name string, seq int, err error) { 627 errPrefix := func() string { 628 return fmt.Sprintf("cannot parse validation set %q", arg) 629 } 630 parts := strings.Split(arg, "=") 631 if len(parts) > 2 { 632 return "", "", 0, fmt.Errorf("%s: expected account/name=seq", errPrefix()) 633 } 634 if len(parts) == 2 { 635 seq, err = strconv.Atoi(parts[1]) 636 if err != nil { 637 return "", "", 0, fmt.Errorf("%s: invalid sequence: %v", errPrefix(), err) 638 } 639 } 640 641 parts = strings.Split(parts[0], "/") 642 if len(parts) != 2 { 643 return "", "", 0, fmt.Errorf("%s: expected a single account/name", errPrefix()) 644 } 645 646 account = parts[0] 647 name = parts[1] 648 if !asserts.IsValidAccountID(account) { 649 return "", "", 0, fmt.Errorf("%s: invalid account ID %q", errPrefix(), account) 650 } 651 if !asserts.IsValidValidationSetName(name) { 652 return "", "", 0, fmt.Errorf("%s: invalid validation set name %q", errPrefix(), name) 653 } 654 655 return account, name, seq, nil 656 }