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