github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 "strings" 29 30 "github.com/snapcore/snapd/asserts" 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/state" 35 ) 36 37 var ( 38 validationSetsListCmd = &Command{ 39 Path: "/v2/validation-sets", 40 GET: listValidationSets, 41 } 42 43 validationSetsCmd = &Command{ 44 Path: "/v2/validation-sets/{account}/{name}", 45 GET: getValidationSet, 46 POST: applyValidationSet, 47 } 48 ) 49 50 type validationSetResult struct { 51 AccountID string `json:"account-id"` 52 Name string `json:"name"` 53 PinnedAt int `json:"pinned-at,omitempty"` 54 Mode string `json:"mode"` 55 Sequence int `json:"sequence,omitempty"` 56 Valid bool `json:"valid"` 57 // TODO: attributes for Notes column 58 } 59 60 func modeString(mode assertstate.ValidationSetMode) (string, error) { 61 switch mode { 62 case assertstate.Monitor: 63 return "monitor", nil 64 case assertstate.Enforce: 65 return "enforce", nil 66 } 67 return "", fmt.Errorf("internal error: unhandled mode %d", mode) 68 } 69 70 func validationSetNotFound(accountID, name string, sequence int) Response { 71 v := map[string]interface{}{ 72 "account-id": accountID, 73 "name": name, 74 } 75 if sequence != 0 { 76 v["sequence"] = sequence 77 } 78 res := &errorResult{ 79 Message: "validation set not found", 80 Kind: client.ErrorKindValidationSetNotFound, 81 Value: v, 82 } 83 return &resp{ 84 Type: ResponseTypeError, 85 Result: res, 86 Status: 404, 87 } 88 } 89 90 func validationSetAccountAndName(validationSet string) (accountID, name string, err error) { 91 parts := strings.Split(validationSet, "/") 92 if len(parts) != 2 { 93 return "", "", fmt.Errorf("invalid validation-set argument") 94 } 95 accountID = parts[0] 96 name = parts[1] 97 if !asserts.IsValidAccountID(accountID) { 98 return "", "", fmt.Errorf("invalid account ID %q", accountID) 99 } 100 if !asserts.IsValidValidationSetName(name) { 101 return "", "", fmt.Errorf("invalid validation set name %q", name) 102 } 103 return accountID, name, nil 104 } 105 106 func listValidationSets(c *Command, r *http.Request, _ *auth.UserState) Response { 107 st := c.d.overlord.State() 108 st.Lock() 109 defer st.Unlock() 110 111 validationSets, err := assertstate.ValidationSets(st) 112 if err != nil { 113 return InternalError("accessing validation sets failed: %v", err) 114 } 115 116 names := make([]string, 0, len(validationSets)) 117 for k := range validationSets { 118 names = append(names, k) 119 } 120 sort.Strings(names) 121 122 results := make([]validationSetResult, len(names)) 123 for i, vs := range names { 124 tr := validationSets[vs] 125 // TODO: evaluate against installed snaps 126 var valid bool 127 modeStr, err := modeString(tr.Mode) 128 if err != nil { 129 return InternalError(err.Error()) 130 } 131 results[i] = validationSetResult{ 132 AccountID: tr.AccountID, 133 Name: tr.Name, 134 PinnedAt: tr.PinnedAt, 135 Mode: modeStr, 136 Sequence: tr.Current, 137 Valid: valid, 138 } 139 } 140 141 return SyncResponse(results, nil) 142 } 143 144 func getValidationSet(c *Command, r *http.Request, _ *auth.UserState) Response { 145 vars := muxVars(r) 146 accountID := vars["account"] 147 name := vars["name"] 148 149 if !asserts.IsValidAccountID(accountID) { 150 return BadRequest("invalid account ID %q", accountID) 151 } 152 if !asserts.IsValidValidationSetName(name) { 153 return BadRequest("invalid name %q", name) 154 } 155 156 query := r.URL.Query() 157 158 // sequence is optional 159 sequenceStr := query.Get("sequence") 160 var sequence int 161 if sequenceStr != "" { 162 var err error 163 sequence, err = strconv.Atoi(sequenceStr) 164 if err != nil { 165 return BadRequest("invalid sequence argument") 166 } 167 if sequence < 0 { 168 return BadRequest("invalid sequence argument: %d", sequence) 169 } 170 } 171 172 st := c.d.overlord.State() 173 st.Lock() 174 defer st.Unlock() 175 176 var tr assertstate.ValidationSetTracking 177 err := assertstate.GetValidationSet(st, accountID, name, &tr) 178 if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) { 179 // TODO: not available locally, try to find in the store. 180 return validationSetNotFound(accountID, name, sequence) 181 } 182 if err != nil { 183 return InternalError("accessing validation sets failed: %v", err) 184 } 185 186 modeStr, err := modeString(tr.Mode) 187 if err != nil { 188 return InternalError(err.Error()) 189 } 190 // TODO: evaluate against installed snaps 191 var valid bool 192 res := validationSetResult{ 193 AccountID: tr.AccountID, 194 Name: tr.Name, 195 PinnedAt: tr.PinnedAt, 196 Mode: modeStr, 197 Sequence: tr.Current, 198 Valid: valid, 199 } 200 return SyncResponse(res, nil) 201 } 202 203 type validationSetApplyRequest struct { 204 Action string `json:"action"` 205 Mode string `json:"mode"` 206 Sequence int `json:"sequence,omitempty"` 207 } 208 209 func applyValidationSet(c *Command, r *http.Request, _ *auth.UserState) Response { 210 vars := muxVars(r) 211 accountID := vars["account"] 212 name := vars["name"] 213 214 if !asserts.IsValidAccountID(accountID) { 215 return BadRequest("invalid account ID %q", accountID) 216 } 217 if !asserts.IsValidValidationSetName(name) { 218 return BadRequest("invalid name %q", name) 219 } 220 221 var req validationSetApplyRequest 222 decoder := json.NewDecoder(r.Body) 223 if err := decoder.Decode(&req); err != nil { 224 return BadRequest("cannot decode request body into validation set action: %v", err) 225 } 226 if decoder.More() { 227 return BadRequest("extra content found in request body") 228 } 229 if req.Sequence < 0 { 230 return BadRequest("invalid sequence argument: %d", req.Sequence) 231 } 232 233 st := c.d.overlord.State() 234 st.Lock() 235 defer st.Unlock() 236 237 switch req.Action { 238 case "forget": 239 return forgetValidationSet(st, accountID, name, req.Sequence) 240 case "apply": 241 return updateValidationSet(st, accountID, name, req.Mode, req.Sequence) 242 default: 243 return BadRequest("unsupported action %q", req.Action) 244 } 245 } 246 247 func updateValidationSet(st *state.State, accountID, name string, reqMode string, sequence int) Response { 248 var mode assertstate.ValidationSetMode 249 switch reqMode { 250 case "monitor": 251 mode = assertstate.Monitor 252 case "enforce": 253 mode = assertstate.Enforce 254 default: 255 return BadRequest("invalid mode %q", reqMode) 256 } 257 258 // TODO: if pinned, check if we have the needed assertion locally; 259 // check with the store if there is something newer there; 260 // check what is the latest in the store if the assertion is not pinned. 261 262 tr := assertstate.ValidationSetTracking{ 263 AccountID: accountID, 264 Name: name, 265 Mode: mode, 266 // note, Sequence may be 0, meaning not pinned. 267 PinnedAt: sequence, 268 } 269 270 // TODO: if the mode is enforced check that the assertion is valid before 271 // saving it; for one from the store add it to the assertion db. 272 273 assertstate.UpdateValidationSet(st, &tr) 274 return SyncResponse(nil, nil) 275 } 276 277 // forgetValidationSet forgets the validation set. 278 // The state needs to be locked by the caller. 279 func forgetValidationSet(st *state.State, accountID, name string, sequence int) Response { 280 // check if it exists first 281 var tr assertstate.ValidationSetTracking 282 err := assertstate.GetValidationSet(st, accountID, name, &tr) 283 if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) { 284 return validationSetNotFound(accountID, name, sequence) 285 } 286 if err != nil { 287 return InternalError("accessing validation sets failed: %v", err) 288 } 289 assertstate.DeleteValidationSet(st, accountID, name) 290 return SyncResponse(nil, nil) 291 }