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