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