github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/daemon/api_validate.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 daemon 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "sort" 27 "strconv" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/asserts/snapasserts" 31 "github.com/snapcore/snapd/client" 32 "github.com/snapcore/snapd/overlord/assertstate" 33 "github.com/snapcore/snapd/overlord/auth" 34 "github.com/snapcore/snapd/overlord/snapstate" 35 "github.com/snapcore/snapd/overlord/state" 36 "github.com/snapcore/snapd/release" 37 ) 38 39 var ( 40 validationSetsListCmd = &Command{ 41 Path: "/v2/validation-sets", 42 GET: listValidationSets, 43 ReadAccess: authenticatedAccess{}, 44 } 45 46 validationSetsCmd = &Command{ 47 Path: "/v2/validation-sets/{account}/{name}", 48 GET: getValidationSet, 49 POST: applyValidationSet, 50 ReadAccess: authenticatedAccess{}, 51 WriteAccess: authenticatedAccess{}, 52 } 53 ) 54 55 type validationSetResult struct { 56 AccountID string `json:"account-id"` 57 Name string `json:"name"` 58 PinnedAt int `json:"pinned-at,omitempty"` 59 Mode string `json:"mode,omitempty"` 60 Sequence int `json:"sequence,omitempty"` 61 Valid bool `json:"valid"` 62 // TODO: attributes for Notes column 63 } 64 65 func modeString(mode assertstate.ValidationSetMode) (string, error) { 66 switch mode { 67 case assertstate.Monitor: 68 return "monitor", nil 69 case assertstate.Enforce: 70 return "enforce", nil 71 } 72 return "", fmt.Errorf("internal error: unhandled mode %d", mode) 73 } 74 75 func validationSetNotFound(accountID, name string, sequence int) Response { 76 v := map[string]interface{}{ 77 "account-id": accountID, 78 "name": name, 79 } 80 if sequence != 0 { 81 v["sequence"] = sequence 82 } 83 return &apiError{ 84 Status: 404, 85 Message: "validation set not found", 86 Kind: client.ErrorKindValidationSetNotFound, 87 Value: v, 88 } 89 } 90 91 func listValidationSets(c *Command, r *http.Request, _ *auth.UserState) Response { 92 st := c.d.overlord.State() 93 st.Lock() 94 defer st.Unlock() 95 96 validationSets, err := assertstate.ValidationSets(st) 97 if err != nil { 98 return InternalError("accessing validation sets failed: %v", err) 99 } 100 101 names := make([]string, 0, len(validationSets)) 102 for k := range validationSets { 103 names = append(names, k) 104 } 105 sort.Strings(names) 106 107 snaps, err := installedSnaps(st) 108 if err != nil { 109 return InternalError(err.Error()) 110 } 111 112 results := make([]validationSetResult, len(names)) 113 for i, vs := range names { 114 tr := validationSets[vs] 115 sequence := tr.Current 116 if tr.PinnedAt > 0 { 117 sequence = tr.PinnedAt 118 } 119 sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence) 120 if err != nil { 121 return InternalError("cannot get assertion for validation set tracking %s/%s/%d: %v", tr.AccountID, tr.Name, sequence, err) 122 } 123 validErr := checkInstalledSnaps(sets, snaps) 124 modeStr, err := modeString(tr.Mode) 125 if err != nil { 126 return InternalError(err.Error()) 127 } 128 results[i] = validationSetResult{ 129 AccountID: tr.AccountID, 130 Name: tr.Name, 131 PinnedAt: tr.PinnedAt, 132 Mode: modeStr, 133 Sequence: tr.Current, 134 Valid: validErr == nil, 135 } 136 } 137 138 return SyncResponse(results) 139 } 140 141 var checkInstalledSnaps = func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error { 142 return vsets.CheckInstalledSnaps(snaps) 143 } 144 145 func installedSnaps(st *state.State) ([]*snapasserts.InstalledSnap, error) { 146 var snaps []*snapasserts.InstalledSnap 147 all, err := snapstate.All(st) 148 if err != nil { 149 return nil, err 150 } 151 for _, snapState := range all { 152 cur, err := snapState.CurrentInfo() 153 if err != nil { 154 return nil, err 155 } 156 snaps = append(snaps, 157 snapasserts.NewInstalledSnap(snapState.InstanceName(), 158 snapState.CurrentSideInfo().SnapID, 159 cur.Revision)) 160 } 161 return snaps, nil 162 } 163 164 func getValidationSet(c *Command, r *http.Request, user *auth.UserState) Response { 165 vars := muxVars(r) 166 accountID := vars["account"] 167 name := vars["name"] 168 169 if !asserts.IsValidAccountID(accountID) { 170 return BadRequest("invalid account ID %q", accountID) 171 } 172 if !asserts.IsValidValidationSetName(name) { 173 return BadRequest("invalid name %q", name) 174 } 175 176 query := r.URL.Query() 177 178 // sequence is optional 179 sequenceStr := query.Get("sequence") 180 var sequence int 181 if sequenceStr != "" { 182 var err error 183 sequence, err = strconv.Atoi(sequenceStr) 184 if err != nil { 185 return BadRequest("invalid sequence argument") 186 } 187 if sequence < 0 { 188 return BadRequest("invalid sequence argument: %d", sequence) 189 } 190 } 191 192 st := c.d.overlord.State() 193 st.Lock() 194 defer st.Unlock() 195 196 var tr assertstate.ValidationSetTracking 197 err := assertstate.GetValidationSet(st, accountID, name, &tr) 198 if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) { 199 // not available locally, try to find in the store. 200 return validateAgainstStore(st, accountID, name, sequence, user) 201 } 202 if err != nil { 203 return InternalError("accessing validation sets failed: %v", err) 204 } 205 206 modeStr, err := modeString(tr.Mode) 207 if err != nil { 208 return InternalError(err.Error()) 209 } 210 211 // evaluate against installed snaps 212 213 if tr.PinnedAt > 0 { 214 sequence = tr.PinnedAt 215 } else { 216 sequence = tr.Current 217 } 218 sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence) 219 if err != nil { 220 return InternalError(err.Error()) 221 } 222 snaps, err := installedSnaps(st) 223 if err != nil { 224 return InternalError(err.Error()) 225 } 226 227 validErr := checkInstalledSnaps(sets, snaps) 228 res := validationSetResult{ 229 AccountID: tr.AccountID, 230 Name: tr.Name, 231 PinnedAt: tr.PinnedAt, 232 Mode: modeStr, 233 Sequence: tr.Current, 234 Valid: validErr == nil, 235 } 236 return SyncResponse(res) 237 } 238 239 type validationSetApplyRequest struct { 240 Action string `json:"action"` 241 Mode string `json:"mode"` 242 Sequence int `json:"sequence,omitempty"` 243 } 244 245 func applyValidationSet(c *Command, r *http.Request, user *auth.UserState) Response { 246 vars := muxVars(r) 247 accountID := vars["account"] 248 name := vars["name"] 249 250 if !asserts.IsValidAccountID(accountID) { 251 return BadRequest("invalid account ID %q", accountID) 252 } 253 if !asserts.IsValidValidationSetName(name) { 254 return BadRequest("invalid name %q", name) 255 } 256 257 var req validationSetApplyRequest 258 decoder := json.NewDecoder(r.Body) 259 if err := decoder.Decode(&req); err != nil { 260 return BadRequest("cannot decode request body into validation set action: %v", err) 261 } 262 if decoder.More() { 263 return BadRequest("extra content found in request body") 264 } 265 if req.Sequence < 0 { 266 return BadRequest("invalid sequence argument: %d", req.Sequence) 267 } 268 269 st := c.d.overlord.State() 270 st.Lock() 271 defer st.Unlock() 272 273 switch req.Action { 274 case "forget": 275 return forgetValidationSet(st, accountID, name, req.Sequence) 276 case "apply": 277 return updateValidationSet(st, accountID, name, req.Mode, req.Sequence, user) 278 default: 279 return BadRequest("unsupported action %q", req.Action) 280 } 281 } 282 283 var validationSetAssertionForMonitor = assertstate.ValidationSetAssertionForMonitor 284 285 // updateValidationSet handles snap validate --monitor and --enforce accountId/name[=sequence]. 286 func updateValidationSet(st *state.State, accountID, name string, reqMode string, sequence int, user *auth.UserState) Response { 287 var mode assertstate.ValidationSetMode 288 // TODO: only monitor mode for now, add enforce. 289 switch reqMode { 290 case "monitor": 291 mode = assertstate.Monitor 292 default: 293 return BadRequest("invalid mode %q", reqMode) 294 } 295 296 tr := assertstate.ValidationSetTracking{ 297 AccountID: accountID, 298 Name: name, 299 Mode: mode, 300 // note, Sequence may be 0, meaning not pinned. 301 PinnedAt: sequence, 302 } 303 304 userID := 0 305 if user != nil { 306 userID = user.ID 307 } 308 pinned := sequence > 0 309 opts := assertstate.ResolveOptions{AllowLocalFallback: true} 310 as, local, err := validationSetAssertionForMonitor(st, accountID, name, sequence, pinned, userID, &opts) 311 if err != nil { 312 return BadRequest("cannot get validation set assertion for %v: %v", assertstate.ValidationSetKey(accountID, name), err) 313 } 314 tr.Current = as.Sequence() 315 tr.LocalOnly = local 316 317 assertstate.UpdateValidationSet(st, &tr) 318 return SyncResponse(nil) 319 } 320 321 // forgetValidationSet forgets the validation set. 322 // The state needs to be locked by the caller. 323 func forgetValidationSet(st *state.State, accountID, name string, sequence int) Response { 324 // check if it exists first 325 var tr assertstate.ValidationSetTracking 326 err := assertstate.GetValidationSet(st, accountID, name, &tr) 327 if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) { 328 return validationSetNotFound(accountID, name, sequence) 329 } 330 if err != nil { 331 return InternalError("accessing validation sets failed: %v", err) 332 } 333 assertstate.DeleteValidationSet(st, accountID, name) 334 return SyncResponse(nil) 335 } 336 337 func validationSetForAssert(st *state.State, accountID, name string, sequence int) (*snapasserts.ValidationSets, error) { 338 as, err := validationSetAssertFromDb(st, accountID, name, sequence) 339 if err != nil { 340 return nil, err 341 } 342 sets := snapasserts.NewValidationSets() 343 if err := sets.Add(as); err != nil { 344 return nil, err 345 } 346 return sets, nil 347 } 348 349 func validationSetAssertFromDb(st *state.State, accountID, name string, sequence int) (*asserts.ValidationSet, error) { 350 headers := map[string]string{ 351 "series": release.Series, 352 "account-id": accountID, 353 "name": name, 354 "sequence": fmt.Sprintf("%d", sequence), 355 } 356 db := assertstate.DB(st) 357 as, err := db.Find(asserts.ValidationSetType, headers) 358 if err != nil { 359 return nil, err 360 } 361 vset := as.(*asserts.ValidationSet) 362 return vset, nil 363 } 364 365 func validateAgainstStore(st *state.State, accountID, name string, sequence int, user *auth.UserState) Response { 366 // get from the store 367 as, err := getSingleSeqFormingAssertion(st, accountID, name, sequence, user) 368 if _, ok := err.(*asserts.NotFoundError); ok { 369 // not in the store - try to find in the database 370 as, err = validationSetAssertFromDb(st, accountID, name, sequence) 371 if _, ok := err.(*asserts.NotFoundError); ok { 372 return validationSetNotFound(accountID, name, sequence) 373 } 374 } 375 if err != nil { 376 return InternalError(err.Error()) 377 } 378 sets := snapasserts.NewValidationSets() 379 vset := as.(*asserts.ValidationSet) 380 if err := sets.Add(vset); err != nil { 381 return InternalError(err.Error()) 382 } 383 snaps, err := installedSnaps(st) 384 if err != nil { 385 return InternalError(err.Error()) 386 } 387 388 validErr := checkInstalledSnaps(sets, snaps) 389 res := validationSetResult{ 390 AccountID: vset.AccountID(), 391 Name: vset.Name(), 392 Sequence: vset.Sequence(), 393 // TODO: pass actual err details and implement "verbose" mode 394 // for the client? 395 Valid: validErr == nil, 396 } 397 return SyncResponse(res) 398 } 399 400 func getSingleSeqFormingAssertion(st *state.State, accountID, name string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 401 sto := snapstate.Store(st, nil) 402 at := asserts.Type("validation-set") 403 if at == nil { 404 panic("validation-set assert type not found") 405 } 406 407 sequenceKey := []string{release.Series, accountID, name} 408 st.Unlock() 409 as, err := sto.SeqFormingAssertion(at, sequenceKey, sequence, user) 410 st.Lock() 411 if err != nil { 412 return nil, err 413 } 414 415 return as, nil 416 }