github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/validation_set.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 asserts 21 22 import ( 23 "fmt" 24 "regexp" 25 "strings" 26 "time" 27 28 "github.com/snapcore/snapd/snap/naming" 29 "github.com/snapcore/snapd/strutil" 30 ) 31 32 // Presence represents a presence constraint. 33 type Presence string 34 35 const ( 36 PresenceRequired Presence = "required" 37 PresenceOptional Presence = "optional" 38 PresenceInvalid Presence = "invalid" 39 ) 40 41 func presencesAsStrings(presences ...Presence) []string { 42 strs := make([]string, len(presences)) 43 for i, pres := range presences { 44 strs[i] = string(pres) 45 } 46 return strs 47 } 48 49 var validValidationSetSnapPresences = presencesAsStrings(PresenceRequired, PresenceOptional, PresenceInvalid) 50 51 func checkPresence(snap map[string]interface{}, which string, valid []string) (Presence, error) { 52 presence, err := checkOptionalStringWhat(snap, "presence", which) 53 if err != nil { 54 return Presence(""), err 55 } 56 if presence != "" && !strutil.ListContains(valid, presence) { 57 return Presence(""), fmt.Errorf("presence %s must be one of %s", which, strings.Join(valid, "|")) 58 } 59 return Presence(presence), nil 60 } 61 62 // ValidationSetSnap holds the details about a snap constrained by a validation-set assertion. 63 type ValidationSetSnap struct { 64 Name string 65 SnapID string 66 67 Presence Presence 68 69 Revision int 70 } 71 72 // SnapName implements naming.SnapRef. 73 func (s *ValidationSetSnap) SnapName() string { 74 return s.Name 75 } 76 77 // ID implements naming.SnapRef. 78 func (s *ValidationSetSnap) ID() string { 79 return s.SnapID 80 } 81 82 func checkValidationSetSnap(snap map[string]interface{}) (*ValidationSetSnap, error) { 83 name, err := checkNotEmptyStringWhat(snap, "name", "of snap") 84 if err != nil { 85 return nil, err 86 } 87 if err := naming.ValidateSnap(name); err != nil { 88 return nil, fmt.Errorf("invalid snap name %q", name) 89 } 90 91 what := fmt.Sprintf("of snap %q", name) 92 93 snapID, err := checkStringMatchesWhat(snap, "id", what, naming.ValidSnapID) 94 if err != nil { 95 return nil, err 96 } 97 98 presence, err := checkPresence(snap, what, validValidationSetSnapPresences) 99 if err != nil { 100 return nil, err 101 } 102 103 var snapRevision int 104 if _, ok := snap["revision"]; ok { 105 var err error 106 snapRevision, err = checkSnapRevisionWhat(snap, "revision", what) 107 if err != nil { 108 return nil, err 109 } 110 } 111 if snapRevision != 0 && presence == PresenceInvalid { 112 return nil, fmt.Errorf(`cannot specify revision %s at the same time as stating its presence is invalid`, what) 113 } 114 115 return &ValidationSetSnap{ 116 Name: name, 117 SnapID: snapID, 118 Presence: presence, 119 Revision: snapRevision, 120 }, nil 121 } 122 123 func checkValidationSetSnaps(snapList interface{}) ([]*ValidationSetSnap, error) { 124 const wrongHeaderType = `"snaps" header must be a list of maps` 125 126 entries, ok := snapList.([]interface{}) 127 if !ok { 128 return nil, fmt.Errorf(wrongHeaderType) 129 } 130 131 seen := make(map[string]bool, len(entries)) 132 seenIDs := make(map[string]string, len(entries)) 133 snaps := make([]*ValidationSetSnap, 0, len(entries)) 134 for _, entry := range entries { 135 snap, ok := entry.(map[string]interface{}) 136 if !ok { 137 return nil, fmt.Errorf(wrongHeaderType) 138 } 139 valSetSnap, err := checkValidationSetSnap(snap) 140 if err != nil { 141 return nil, err 142 } 143 144 if seen[valSetSnap.Name] { 145 return nil, fmt.Errorf("cannot list the same snap %q multiple times", valSetSnap.Name) 146 } 147 seen[valSetSnap.Name] = true 148 snapID := valSetSnap.SnapID 149 if underName := seenIDs[snapID]; underName != "" { 150 return nil, fmt.Errorf("cannot specify the same snap id %q multiple times, specified for snaps %q and %q", snapID, underName, valSetSnap.Name) 151 } 152 seenIDs[snapID] = valSetSnap.Name 153 154 if valSetSnap.Presence == "" { 155 valSetSnap.Presence = PresenceRequired 156 } 157 158 snaps = append(snaps, valSetSnap) 159 } 160 161 return snaps, nil 162 } 163 164 // ValidationSet holds a validation-set assertion, which is a 165 // statement by an account about a set snaps and possibly revisions 166 // for which an extrinsic/implied property is valid (e.g. they work 167 // well together). validation-sets are organized in sequences under a 168 // name. 169 type ValidationSet struct { 170 assertionBase 171 172 seq int 173 174 snaps []*ValidationSetSnap 175 176 timestamp time.Time 177 } 178 179 // Series returns the series for which the snap in the set are declared. 180 func (vs *ValidationSet) Series() string { 181 return vs.HeaderString("series") 182 } 183 184 // AccountID returns the identifier of the account that signed this assertion. 185 func (vs *ValidationSet) AccountID() string { 186 return vs.HeaderString("account-id") 187 } 188 189 // Name returns the name under which the validation-set is organized. 190 func (vs *ValidationSet) Name() string { 191 return vs.HeaderString("name") 192 } 193 194 // Sequence returns the sequential number of the validation-set in its 195 // named sequence. 196 func (vs *ValidationSet) Sequence() int { 197 return vs.seq 198 } 199 200 // Snaps returns the constrained snaps by the validation-set. 201 func (vs *ValidationSet) Snaps() []*ValidationSetSnap { 202 return vs.snaps 203 } 204 205 // Timestamp returns the time when the validation-set was issued. 206 func (vs *ValidationSet) Timestamp() time.Time { 207 return vs.timestamp 208 } 209 210 func checkSequence(headers map[string]interface{}, name string) (int, error) { 211 seqnum, err := checkInt(headers, name) 212 if err != nil { 213 return -1, err 214 } 215 if seqnum < 1 { 216 return -1, fmt.Errorf("%q must be >=1: %v", name, seqnum) 217 } 218 return seqnum, nil 219 } 220 221 var ( 222 validValidationSetName = regexp.MustCompile("^[a-z0-9](?:-?[a-z0-9])*$") 223 ) 224 225 func assembleValidationSet(assert assertionBase) (Assertion, error) { 226 authorityID := assert.AuthorityID() 227 accountID := assert.HeaderString("account-id") 228 if accountID != authorityID { 229 return nil, fmt.Errorf("authority-id and account-id must match, validation-set assertions are expected to be signed by the issuer account: %q != %q", authorityID, accountID) 230 } 231 232 _, err := checkStringMatches(assert.headers, "name", validValidationSetName) 233 if err != nil { 234 return nil, err 235 } 236 237 seq, err := checkSequence(assert.headers, "sequence") 238 if err != nil { 239 return nil, err 240 } 241 242 snapList, ok := assert.headers["snaps"] 243 if !ok { 244 return nil, fmt.Errorf(`"snaps" header is mandatory`) 245 } 246 snaps, err := checkValidationSetSnaps(snapList) 247 if err != nil { 248 return nil, err 249 } 250 251 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 252 if err != nil { 253 return nil, err 254 } 255 256 return &ValidationSet{ 257 assertionBase: assert, 258 seq: seq, 259 snaps: snaps, 260 timestamp: timestamp, 261 }, nil 262 } 263 264 func IsValidValidationSetName(name string) bool { 265 return validValidationSetName.MatchString(name) 266 }