github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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 "strings" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/snap" 30 "github.com/snapcore/snapd/snap/naming" 31 ) 32 33 // InstalledSnap holds the minimal details about an installed snap required to 34 // check it against validation sets. 35 type InstalledSnap struct { 36 naming.SnapRef 37 Revision snap.Revision 38 } 39 40 // NewInstalledSnap creates InstalledSnap. 41 func NewInstalledSnap(name, snapID string, revision snap.Revision) *InstalledSnap { 42 return &InstalledSnap{ 43 SnapRef: naming.NewSnapRef(name, snapID), 44 Revision: revision, 45 } 46 } 47 48 // ValidationSetsConflictError describes an error where multiple 49 // validation sets are in conflict about snaps. 50 type ValidationSetsConflictError struct { 51 Sets map[string]*asserts.ValidationSet 52 Snaps map[string]error 53 } 54 55 func (e *ValidationSetsConflictError) Error() string { 56 buf := bytes.NewBufferString("validation sets are in conflict:") 57 for _, err := range e.Snaps { 58 fmt.Fprintf(buf, "\n- %v", err) 59 } 60 return buf.String() 61 } 62 63 // ValidationSetsValidationError describes an error arising 64 // from validation of snaps against ValidationSets. 65 type ValidationSetsValidationError struct { 66 // MissingSnaps maps missing snap names to the validation sets requiring them. 67 MissingSnaps map[string][]string 68 // InvalidSnaps maps snap names to the validation sets declaring them invalid. 69 InvalidSnaps map[string][]string 70 // WronRevisionSnaps maps snap names to the expected revisions and respective 71 // validation sets that require them. 72 WrongRevisionSnaps map[string]map[snap.Revision][]string 73 // Sets maps validation set keys referenced by above maps to actual 74 // validation sets. 75 Sets map[string]*asserts.ValidationSet 76 } 77 78 type byRevision []snap.Revision 79 80 func (b byRevision) Len() int { return len(b) } 81 func (b byRevision) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 82 func (b byRevision) Less(i, j int) bool { return b[i].N < b[j].N } 83 84 func (e *ValidationSetsValidationError) Error() string { 85 buf := bytes.NewBufferString("validation sets assertions are not met:") 86 printDetails := func(header string, details map[string][]string, 87 printSnap func(snapName string, keys []string) string) { 88 if len(details) == 0 { 89 return 90 } 91 fmt.Fprintf(buf, "\n- %s:", header) 92 for snapName, validationSetKeys := range details { 93 fmt.Fprintf(buf, "\n - %s", printSnap(snapName, validationSetKeys)) 94 } 95 } 96 97 printDetails("missing required snaps", e.MissingSnaps, func(snapName string, validationSetKeys []string) string { 98 return fmt.Sprintf("%s (required by sets %s)", snapName, strings.Join(validationSetKeys, ",")) 99 }) 100 printDetails("invalid snaps", e.InvalidSnaps, func(snapName string, validationSetKeys []string) string { 101 return fmt.Sprintf("%s (invalid for sets %s)", snapName, strings.Join(validationSetKeys, ",")) 102 }) 103 104 if len(e.WrongRevisionSnaps) > 0 { 105 fmt.Fprint(buf, "\n- snaps at wrong revisions:") 106 for snapName, revisions := range e.WrongRevisionSnaps { 107 revisionsSorted := make([]snap.Revision, 0, len(revisions)) 108 for rev := range revisions { 109 revisionsSorted = append(revisionsSorted, rev) 110 } 111 sort.Sort(byRevision(revisionsSorted)) 112 t := make([]string, 0, len(revisionsSorted)) 113 for _, rev := range revisionsSorted { 114 keys := revisions[rev] 115 t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ","))) 116 } 117 fmt.Fprintf(buf, "\n - %s (required %s)", snapName, strings.Join(t, ", ")) 118 } 119 } 120 121 return buf.String() 122 } 123 124 // ValidationSets can hold a combination of validation-set assertions 125 // and can check for conflicts or help applying them. 126 type ValidationSets struct { 127 // sets maps sequence keys to validation-set in the combination 128 sets map[string]*asserts.ValidationSet 129 // snaps maps snap-ids to snap constraints 130 snaps map[string]*snapContraints 131 } 132 133 const presConflict asserts.Presence = "conflict" 134 135 var unspecifiedRevision = snap.R(0) 136 var invalidPresRevision = snap.R(-1) 137 138 type snapContraints struct { 139 name string 140 presence asserts.Presence 141 // revisions maps revisions to pairing of ValidationSetSnap 142 // and the originating validation-set key 143 // * unspecifiedRevision is used for constraints without a 144 // revision 145 // * invalidPresRevision is used for constraints that mark 146 // presence as invalid 147 revisions map[snap.Revision][]*revConstraint 148 } 149 150 type revConstraint struct { 151 validationSetKey string 152 asserts.ValidationSetSnap 153 } 154 155 func (c *snapContraints) conflict() *snapConflictsError { 156 if c.presence != presConflict { 157 return nil 158 } 159 160 const dontCare asserts.Presence = "" 161 whichSets := func(rcs []*revConstraint, presence asserts.Presence) []string { 162 which := make([]string, 0, len(rcs)) 163 for _, rc := range rcs { 164 if presence != dontCare && rc.Presence != presence { 165 continue 166 } 167 which = append(which, rc.validationSetKey) 168 } 169 if len(which) == 0 { 170 return nil 171 } 172 sort.Strings(which) 173 return which 174 } 175 176 byRev := make(map[snap.Revision][]string, len(c.revisions)) 177 for r := range c.revisions { 178 pres := dontCare 179 switch r { 180 case invalidPresRevision: 181 pres = asserts.PresenceInvalid 182 case unspecifiedRevision: 183 pres = asserts.PresenceRequired 184 } 185 which := whichSets(c.revisions[r], pres) 186 if len(which) != 0 { 187 byRev[r] = which 188 } 189 } 190 191 return &snapConflictsError{ 192 name: c.name, 193 revisions: byRev, 194 } 195 } 196 197 type snapConflictsError struct { 198 name string 199 // revisions maps revisions to validation-set keys of the sets 200 // that are in conflict over the revision. 201 // * unspecifiedRevision is used for validation-sets conflicting 202 // on the snap by requiring it but without a revision 203 // * invalidPresRevision is used for validation-sets that mark 204 // presence as invalid 205 // see snapContraints.revisions as well 206 revisions map[snap.Revision][]string 207 } 208 209 func (e *snapConflictsError) Error() string { 210 whichSets := func(which []string) string { 211 return fmt.Sprintf("(%s)", strings.Join(which, ",")) 212 } 213 214 msg := fmt.Sprintf("cannot constrain snap %q", e.name) 215 invalid := false 216 if invalidOnes, ok := e.revisions[invalidPresRevision]; ok { 217 msg += fmt.Sprintf(" as both invalid %s and required", whichSets(invalidOnes)) 218 invalid = true 219 } 220 221 var revnos []snap.Revision 222 for r := range e.revisions { 223 if r.N >= 1 { 224 revnos = append(revnos, r) 225 } 226 } 227 if len(revnos) == 1 { 228 msg += fmt.Sprintf(" at revision %s %s", revnos[0], whichSets(e.revisions[revnos[0]])) 229 } else if len(revnos) > 1 { 230 sort.Sort(byRevision(revnos)) 231 l := make([]string, 0, len(revnos)) 232 for _, rev := range revnos { 233 l = append(l, fmt.Sprintf("%s %s", rev, whichSets(e.revisions[rev]))) 234 } 235 msg += fmt.Sprintf(" at different revisions %s", strings.Join(l, ", ")) 236 } 237 238 if unspecifiedOnes, ok := e.revisions[unspecifiedRevision]; ok { 239 which := whichSets(unspecifiedOnes) 240 if which != "" { 241 if len(revnos) != 0 { 242 msg += " or" 243 } 244 if invalid { 245 msg += fmt.Sprintf(" at any revision %s", which) 246 } else { 247 msg += fmt.Sprintf(" required at any revision %s", which) 248 } 249 } 250 } 251 return msg 252 } 253 254 // NewValidationSets returns a new ValidationSets. 255 func NewValidationSets() *ValidationSets { 256 return &ValidationSets{ 257 sets: map[string]*asserts.ValidationSet{}, 258 snaps: map[string]*snapContraints{}, 259 } 260 } 261 262 func valSetKey(valset *asserts.ValidationSet) string { 263 return fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name()) 264 } 265 266 // Add adds the given asserts.ValidationSet to the combination. 267 // It errors if a validation-set with the same sequence key has been 268 // added already. 269 func (v *ValidationSets) Add(valset *asserts.ValidationSet) error { 270 k := valSetKey(valset) 271 if _, ok := v.sets[k]; ok { 272 return fmt.Errorf("cannot add a second validation-set under %q", k) 273 } 274 v.sets[k] = valset 275 for _, sn := range valset.Snaps() { 276 v.addSnap(sn, k) 277 } 278 return nil 279 } 280 281 func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey string) { 282 rev := snap.R(sn.Revision) 283 if sn.Presence == asserts.PresenceInvalid { 284 rev = invalidPresRevision 285 } 286 287 rc := &revConstraint{ 288 validationSetKey: validationSetKey, 289 ValidationSetSnap: *sn, 290 } 291 292 cs := v.snaps[sn.SnapID] 293 if cs == nil { 294 v.snaps[sn.SnapID] = &snapContraints{ 295 name: sn.Name, 296 presence: sn.Presence, 297 revisions: map[snap.Revision][]*revConstraint{ 298 rev: {rc}, 299 }, 300 } 301 return 302 } 303 304 cs.revisions[rev] = append(cs.revisions[rev], rc) 305 if cs.presence == presConflict { 306 // nothing to check anymore 307 return 308 } 309 // this counts really different revisions or invalid 310 ndiff := len(cs.revisions) 311 if _, ok := cs.revisions[unspecifiedRevision]; ok { 312 ndiff-- 313 } 314 switch { 315 case cs.presence == asserts.PresenceOptional: 316 cs.presence = sn.Presence 317 fallthrough 318 case cs.presence == sn.Presence || sn.Presence == asserts.PresenceOptional: 319 if ndiff > 1 { 320 if cs.presence == asserts.PresenceRequired { 321 // different revisions required/invalid 322 cs.presence = presConflict 323 return 324 } 325 // multiple optional at different revisions => invalid 326 cs.presence = asserts.PresenceInvalid 327 } 328 return 329 } 330 // we are left with a combo of required and invalid => conflict 331 cs.presence = presConflict 332 } 333 334 // Conflict returns a non-nil error if the combination is in conflict, 335 // nil otherwise. 336 func (v *ValidationSets) Conflict() error { 337 sets := make(map[string]*asserts.ValidationSet) 338 snaps := make(map[string]error) 339 340 for snapID, snConstrs := range v.snaps { 341 snConflictsErr := snConstrs.conflict() 342 if snConflictsErr != nil { 343 snaps[snapID] = snConflictsErr 344 for _, valsetKeys := range snConflictsErr.revisions { 345 for _, valsetKey := range valsetKeys { 346 sets[valsetKey] = v.sets[valsetKey] 347 } 348 } 349 } 350 } 351 352 if len(snaps) != 0 { 353 return &ValidationSetsConflictError{ 354 Sets: sets, 355 Snaps: snaps, 356 } 357 } 358 return nil 359 } 360 361 // CheckInstalledSnaps checks installed snaps against the validation sets. 362 func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap) error { 363 installed := naming.NewSnapSet(nil) 364 for _, sn := range snaps { 365 installed.Add(sn) 366 } 367 368 // snapName -> validationSet key -> validation set 369 invalid := make(map[string]map[string]bool) 370 missing := make(map[string]map[string]bool) 371 wrongrev := make(map[string]map[snap.Revision]map[string]bool) 372 sets := make(map[string]*asserts.ValidationSet) 373 374 for _, cstrs := range v.snaps { 375 for rev, revCstr := range cstrs.revisions { 376 for _, rc := range revCstr { 377 sn := installed.Lookup(rc) 378 isInstalled := sn != nil 379 380 switch { 381 case !isInstalled && (cstrs.presence == asserts.PresenceOptional || cstrs.presence == asserts.PresenceInvalid): 382 // not installed, but optional or not required 383 case isInstalled && cstrs.presence == asserts.PresenceInvalid: 384 // installed but not expected to be present 385 if invalid[rc.Name] == nil { 386 invalid[rc.Name] = make(map[string]bool) 387 } 388 invalid[rc.Name][rc.validationSetKey] = true 389 sets[rc.validationSetKey] = v.sets[rc.validationSetKey] 390 case isInstalled: 391 // presence is either optional or required 392 if rev != unspecifiedRevision && rev != sn.(*InstalledSnap).Revision { 393 // expected a different revision 394 if wrongrev[rc.Name] == nil { 395 wrongrev[rc.Name] = make(map[snap.Revision]map[string]bool) 396 } 397 if wrongrev[rc.Name][rev] == nil { 398 wrongrev[rc.Name][rev] = make(map[string]bool) 399 } 400 wrongrev[rc.Name][rev][rc.validationSetKey] = true 401 sets[rc.validationSetKey] = v.sets[rc.validationSetKey] 402 } 403 default: 404 // not installed but required 405 if missing[rc.Name] == nil { 406 missing[rc.Name] = make(map[string]bool) 407 } 408 missing[rc.Name][rc.validationSetKey] = true 409 sets[rc.validationSetKey] = v.sets[rc.validationSetKey] 410 } 411 } 412 } 413 } 414 415 setsToLists := func(in map[string]map[string]bool) map[string][]string { 416 if len(in) == 0 { 417 return nil 418 } 419 out := make(map[string][]string) 420 for snap, sets := range in { 421 out[snap] = make([]string, 0, len(sets)) 422 for validationSetKey := range sets { 423 out[snap] = append(out[snap], validationSetKey) 424 } 425 sort.Strings(out[snap]) 426 } 427 return out 428 } 429 430 if len(invalid) > 0 || len(missing) > 0 || len(wrongrev) > 0 { 431 verr := &ValidationSetsValidationError{ 432 InvalidSnaps: setsToLists(invalid), 433 MissingSnaps: setsToLists(missing), 434 Sets: sets, 435 } 436 if len(wrongrev) > 0 { 437 verr.WrongRevisionSnaps = make(map[string]map[snap.Revision][]string) 438 for snapName, revs := range wrongrev { 439 verr.WrongRevisionSnaps[snapName] = make(map[snap.Revision][]string) 440 for rev, keys := range revs { 441 for key := range keys { 442 verr.WrongRevisionSnaps[snapName][rev] = append(verr.WrongRevisionSnaps[snapName][rev], key) 443 } 444 sort.Strings(verr.WrongRevisionSnaps[snapName][rev]) 445 } 446 } 447 } 448 return verr 449 } 450 return nil 451 } 452 453 // PresenceConstraintError describes an error where presence of the given snap 454 // has unexpected value, e.g. it's "invalid" while checking for "required". 455 type PresenceConstraintError struct { 456 SnapName string 457 Presence asserts.Presence 458 } 459 460 func (e *PresenceConstraintError) Error() string { 461 return fmt.Sprintf("unexpected presence %q for snap %q", e.Presence, e.SnapName) 462 } 463 464 func (v *ValidationSets) constraintsForSnap(snapRef naming.SnapRef) *snapContraints { 465 if snapRef.ID() != "" { 466 return v.snaps[snapRef.ID()] 467 } 468 // snapID not available, find by snap name 469 for _, cstrs := range v.snaps { 470 if cstrs.name == snapRef.SnapName() { 471 return cstrs 472 } 473 } 474 return nil 475 } 476 477 // CheckPresenceRequired returns the list of all validation sets that declare 478 // presence of the given snap as required and the required revision (or 479 // snap.R(0) if no specific revision is required). PresenceConstraintError is 480 // returned if presence of the snap is "invalid". 481 // The method assumes that validation sets are not in conflict. 482 func (v *ValidationSets) CheckPresenceRequired(snapRef naming.SnapRef) ([]string, snap.Revision, error) { 483 cstrs := v.constraintsForSnap(snapRef) 484 if cstrs == nil { 485 return nil, unspecifiedRevision, nil 486 } 487 if cstrs.presence == asserts.PresenceInvalid { 488 return nil, unspecifiedRevision, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} 489 } 490 if cstrs.presence != asserts.PresenceRequired { 491 return nil, unspecifiedRevision, nil 492 } 493 494 snapRev := unspecifiedRevision 495 var keys []string 496 for rev, revCstr := range cstrs.revisions { 497 for _, rc := range revCstr { 498 keys = append(keys, rc.validationSetKey) 499 // there may be constraints without revision; only set snapRev if 500 // it wasn't already determined. Note that if revisions are set, 501 // then they are the same, otherwise validation sets would be in 502 // conflict. 503 // This is an equivalent of 'if rev != unspecifiedRevision`. 504 if snapRev == unspecifiedRevision { 505 snapRev = rev 506 } 507 } 508 } 509 510 sort.Strings(keys) 511 return keys, snapRev, nil 512 } 513 514 // CheckPresenceInvalid returns the list of all validation sets that declare 515 // presence of the given snap as invalid. PresenceConstraintError is returned if 516 // presence of the snap is "optional" or "required". 517 // The method assumes that validation sets are not in conflict. 518 func (v *ValidationSets) CheckPresenceInvalid(snapRef naming.SnapRef) ([]string, error) { 519 cstrs := v.constraintsForSnap(snapRef) 520 if cstrs == nil { 521 return nil, nil 522 } 523 if cstrs.presence != asserts.PresenceInvalid { 524 return nil, &PresenceConstraintError{snapRef.SnapName(), cstrs.presence} 525 } 526 var keys []string 527 for _, revCstr := range cstrs.revisions { 528 for _, rc := range revCstr { 529 if rc.Presence == asserts.PresenceInvalid { 530 keys = append(keys, rc.validationSetKey) 531 } 532 } 533 } 534 535 sort.Strings(keys) 536 return keys, nil 537 }