github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/api_model_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 "bytes" 24 "encoding/json" 25 "net/http" 26 "time" 27 28 "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/asserts/assertstest" 32 "github.com/snapcore/snapd/client" 33 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 34 "github.com/snapcore/snapd/overlord/auth" 35 "github.com/snapcore/snapd/overlord/devicestate" 36 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 37 "github.com/snapcore/snapd/overlord/hookstate" 38 "github.com/snapcore/snapd/overlord/state" 39 ) 40 41 func (s *apiSuite) TestPostRemodelUnhappy(c *check.C) { 42 data, err := json.Marshal(postModelData{NewModel: "invalid model"}) 43 c.Check(err, check.IsNil) 44 45 req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data)) 46 c.Assert(err, check.IsNil) 47 rsp := postModel(appsCmd, req, nil).(*resp) 48 c.Check(rsp.Type, check.Equals, ResponseTypeError) 49 c.Assert(rsp.Status, check.Equals, 400) 50 c.Check(rsp.Result.(*errorResult).Message, check.Matches, "cannot decode new model assertion: .*") 51 } 52 53 func (s *apiSuite) TestPostRemodel(c *check.C) { 54 oldModel := s.brands.Model("my-brand", "my-old-model", modelDefaults) 55 newModel := s.brands.Model("my-brand", "my-old-model", modelDefaults, map[string]interface{}{ 56 "revision": "2", 57 }) 58 59 d := s.daemonWithOverlordMock(c) 60 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 61 c.Assert(err, check.IsNil) 62 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 63 c.Assert(err, check.IsNil) 64 d.overlord.AddManager(deviceMgr) 65 st := d.overlord.State() 66 st.Lock() 67 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 68 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 69 s.mockModel(c, st, oldModel) 70 st.Unlock() 71 72 soon := 0 73 ensureStateSoon = func(st *state.State) { 74 soon++ 75 ensureStateSoonImpl(st) 76 } 77 defer func() { ensureStateSoon = func(st *state.State) {} }() 78 79 var devicestateRemodelGotModel *asserts.Model 80 devicestateRemodel = func(st *state.State, nm *asserts.Model) (*state.Change, error) { 81 devicestateRemodelGotModel = nm 82 chg := st.NewChange("remodel", "...") 83 return chg, nil 84 } 85 86 // create a valid model assertion 87 c.Assert(err, check.IsNil) 88 modelEncoded := string(asserts.Encode(newModel)) 89 data, err := json.Marshal(postModelData{NewModel: modelEncoded}) 90 c.Check(err, check.IsNil) 91 92 // set it and validate that this is what we was passed to 93 // devicestateRemodel 94 req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data)) 95 c.Assert(err, check.IsNil) 96 rsp := postModel(appsCmd, req, nil).(*resp) 97 c.Assert(rsp.Status, check.Equals, 202) 98 c.Check(devicestateRemodelGotModel, check.DeepEquals, newModel) 99 100 st.Lock() 101 defer st.Unlock() 102 chg := st.Change(rsp.Change) 103 c.Assert(chg, check.NotNil) 104 105 c.Assert(st.Changes(), check.HasLen, 1) 106 chg1 := st.Changes()[0] 107 c.Assert(chg, check.DeepEquals, chg1) 108 c.Assert(chg.Kind(), check.Equals, "remodel") 109 c.Assert(chg.Err(), check.IsNil) 110 111 c.Assert(soon, check.Equals, 1) 112 } 113 114 func (s *apiSuite) TestGetModelNoModelAssertion(c *check.C) { 115 116 d := s.daemonWithOverlordMock(c) 117 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 118 c.Assert(err, check.IsNil) 119 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 120 c.Assert(err, check.IsNil) 121 d.overlord.AddManager(deviceMgr) 122 123 req, err := http.NewRequest("GET", "/v2/model", nil) 124 c.Assert(err, check.IsNil) 125 response := getModel(appsCmd, req, nil) 126 c.Assert(response, check.FitsTypeOf, &resp{}) 127 rsp := response.(*resp) 128 c.Assert(rsp.Status, check.Equals, 404) 129 c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{}) 130 errRes := rsp.Result.(*errorResult) 131 c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound) 132 c.Assert(errRes.Value, check.Equals, "model") 133 c.Assert(errRes.Message, check.Equals, "no model assertion yet") 134 } 135 136 func (s *apiSuite) TestGetModelHasModelAssertion(c *check.C) { 137 // make a model assertion 138 theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults) 139 140 // model assertion setup 141 d := s.daemonWithOverlordMock(c) 142 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 143 c.Assert(err, check.IsNil) 144 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 145 c.Assert(err, check.IsNil) 146 d.overlord.AddManager(deviceMgr) 147 st := d.overlord.State() 148 st.Lock() 149 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 150 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 151 s.mockModel(c, st, theModel) 152 st.Unlock() 153 154 // make a new get request to the model endpoint 155 req, err := http.NewRequest("GET", "/v2/model", nil) 156 c.Assert(err, check.IsNil) 157 response := getModel(appsCmd, req, nil) 158 159 // check that we get an assertion response 160 c.Assert(response, check.FitsTypeOf, &assertResponse{}) 161 162 // check that there is only one assertion 163 assertions := response.(*assertResponse).assertions 164 c.Assert(assertions, check.HasLen, 1) 165 166 // check that one of the assertion keys matches what's in the model we 167 // provided 168 assert := assertions[0] 169 arch := assert.Header("architecture") 170 c.Assert(arch, check.FitsTypeOf, "") 171 c.Assert(arch.(string), check.Equals, modelDefaults["architecture"]) 172 } 173 174 func (s *apiSuite) TestGetModelJSONHasModelAssertion(c *check.C) { 175 // make a model assertion 176 theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults) 177 178 // model assertion setup 179 d := s.daemonWithOverlordMock(c) 180 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 181 c.Assert(err, check.IsNil) 182 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 183 c.Assert(err, check.IsNil) 184 d.overlord.AddManager(deviceMgr) 185 st := d.overlord.State() 186 st.Lock() 187 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 188 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 189 s.mockModel(c, st, theModel) 190 st.Unlock() 191 192 // make a new get request to the model endpoint with json as true 193 req, err := http.NewRequest("GET", "/v2/model?json=true", nil) 194 c.Assert(err, check.IsNil) 195 response := getModel(appsCmd, req, nil) 196 197 // check that we get an generic response type 198 c.Assert(response, check.FitsTypeOf, &resp{}) 199 200 // get the body and try to unmarshal into modelAssertJSONResponse 201 c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{}) 202 203 jsonResponse := response.(*resp).Result.(modelAssertJSONResponse) 204 205 // get the architecture key from the headers 206 arch, ok := jsonResponse.Headers["architecture"] 207 c.Assert(ok, check.Equals, true) 208 209 // ensure that the architecture key is what we set in the model defaults 210 c.Assert(arch, check.FitsTypeOf, "") 211 c.Assert(arch.(string), check.Equals, modelDefaults["architecture"]) 212 } 213 214 func (s *apiSuite) TestGetModelNoSerialAssertion(c *check.C) { 215 216 d := s.daemonWithOverlordMock(c) 217 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 218 c.Assert(err, check.IsNil) 219 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 220 c.Assert(err, check.IsNil) 221 d.overlord.AddManager(deviceMgr) 222 223 req, err := http.NewRequest("GET", "/v2/model/serial", nil) 224 c.Assert(err, check.IsNil) 225 response := getSerial(appsCmd, req, nil) 226 c.Assert(response, check.FitsTypeOf, &resp{}) 227 rsp := response.(*resp) 228 c.Assert(rsp.Status, check.Equals, 404) 229 c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{}) 230 errRes := rsp.Result.(*errorResult) 231 c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound) 232 c.Assert(errRes.Value, check.Equals, "serial") 233 c.Assert(errRes.Message, check.Equals, "no serial assertion yet") 234 } 235 236 func (s *apiSuite) TestGetModelHasSerialAssertion(c *check.C) { 237 // make a model assertion 238 theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults) 239 240 deviceKey, _ := assertstest.GenerateKey(752) 241 242 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 243 c.Assert(err, check.IsNil) 244 245 // model assertion setup 246 d := s.daemonWithOverlordMock(c) 247 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 248 c.Assert(err, check.IsNil) 249 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 250 c.Assert(err, check.IsNil) 251 d.overlord.AddManager(deviceMgr) 252 st := d.overlord.State() 253 st.Lock() 254 defer st.Unlock() 255 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 256 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 257 s.mockModel(c, st, theModel) 258 259 serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 260 "authority-id": "my-brand", 261 "brand-id": "my-brand", 262 "model": "my-old-model", 263 "serial": "serialserial", 264 "device-key": string(encDevKey), 265 "device-key-sha3-384": deviceKey.PublicKey().ID(), 266 "timestamp": time.Now().Format(time.RFC3339), 267 }, nil, "") 268 c.Assert(err, check.IsNil) 269 assertstatetest.AddMany(st, serial) 270 devicestatetest.SetDevice(st, &auth.DeviceState{ 271 Brand: "my-brand", 272 Model: "my-old-model", 273 Serial: "serialserial", 274 }) 275 276 st.Unlock() 277 defer st.Lock() 278 279 // make a new get request to the serial endpoint 280 req, err := http.NewRequest("GET", "/v2/model/serial", nil) 281 c.Assert(err, check.IsNil) 282 response := getSerial(appsCmd, req, nil) 283 284 // check that we get an assertion response 285 c.Assert(response, check.FitsTypeOf, &assertResponse{}) 286 287 // check that there is only one assertion 288 assertions := response.(*assertResponse).assertions 289 c.Assert(assertions, check.HasLen, 1) 290 291 // check that the device key in the returned assertion matches what we 292 // created above 293 assert := assertions[0] 294 devKey := assert.Header("device-key") 295 c.Assert(devKey, check.FitsTypeOf, "") 296 c.Assert(devKey.(string), check.Equals, string(encDevKey)) 297 } 298 299 func (s *apiSuite) TestGetModelJSONHasSerialAssertion(c *check.C) { 300 // make a model assertion 301 theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults) 302 303 deviceKey, _ := assertstest.GenerateKey(752) 304 305 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 306 c.Assert(err, check.IsNil) 307 308 // model assertion setup 309 d := s.daemonWithOverlordMock(c) 310 hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner()) 311 c.Assert(err, check.IsNil) 312 deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil) 313 c.Assert(err, check.IsNil) 314 d.overlord.AddManager(deviceMgr) 315 st := d.overlord.State() 316 st.Lock() 317 defer st.Unlock() 318 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 319 assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...) 320 s.mockModel(c, st, theModel) 321 322 serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 323 "authority-id": "my-brand", 324 "brand-id": "my-brand", 325 "model": "my-old-model", 326 "serial": "serialserial", 327 "device-key": string(encDevKey), 328 "device-key-sha3-384": deviceKey.PublicKey().ID(), 329 "timestamp": time.Now().Format(time.RFC3339), 330 }, nil, "") 331 c.Assert(err, check.IsNil) 332 assertstatetest.AddMany(st, serial) 333 devicestatetest.SetDevice(st, &auth.DeviceState{ 334 Brand: "my-brand", 335 Model: "my-old-model", 336 Serial: "serialserial", 337 }) 338 339 st.Unlock() 340 defer st.Lock() 341 342 // make a new get request to the model endpoint with json as true 343 req, err := http.NewRequest("GET", "/v2/model/serial?json=true", nil) 344 c.Assert(err, check.IsNil) 345 response := getSerial(appsCmd, req, nil) 346 347 // check that we get an generic response type 348 c.Assert(response, check.FitsTypeOf, &resp{}) 349 350 // get the body and try to unmarshal into modelAssertJSONResponse 351 c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{}) 352 353 jsonResponse := response.(*resp).Result.(modelAssertJSONResponse) 354 355 // get the architecture key from the headers 356 devKey, ok := jsonResponse.Headers["device-key"] 357 c.Assert(ok, check.Equals, true) 358 359 // check that the device key in the returned assertion matches what we 360 // created above 361 c.Assert(devKey, check.FitsTypeOf, "") 362 c.Assert(devKey.(string), check.Equals, string(encDevKey)) 363 }