gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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  }