github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 s.expectRootAccess() 60 61 data, err := json.Marshal(daemon.PostModelData{NewModel: "invalid model"}) 62 c.Check(err, check.IsNil) 63 64 req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data)) 65 c.Assert(err, check.IsNil) 66 rspe := s.errorReq(c, req, nil) 67 c.Assert(rspe.Status, check.Equals, 400) 68 c.Check(rspe.Message, check.Matches, "cannot decode new model assertion: .*") 69 } 70 71 func (s *modelSuite) TestPostRemodel(c *check.C) { 72 s.expectRootAccess() 73 74 oldModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults) 75 newModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults, map[string]interface{}{ 76 "revision": "2", 77 }) 78 79 d := s.daemonWithOverlordMockAndStore(c) 80 hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner()) 81 c.Assert(err, check.IsNil) 82 deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil) 83 c.Assert(err, check.IsNil) 84 d.Overlord().AddManager(deviceMgr) 85 st := d.Overlord().State() 86 st.Lock() 87 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey("")) 88 assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...) 89 s.mockModel(c, st, oldModel) 90 st.Unlock() 91 92 soon := 0 93 var origEnsureStateSoon func(*state.State) 94 origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) { 95 soon++ 96 origEnsureStateSoon(st) 97 }) 98 defer restore() 99 100 var devicestateRemodelGotModel *asserts.Model 101 defer daemon.MockDevicestateRemodel(func(st *state.State, nm *asserts.Model) (*state.Change, error) { 102 devicestateRemodelGotModel = nm 103 chg := st.NewChange("remodel", "...") 104 return chg, nil 105 })() 106 107 // create a valid model assertion 108 c.Assert(err, check.IsNil) 109 modelEncoded := string(asserts.Encode(newModel)) 110 data, err := json.Marshal(daemon.PostModelData{NewModel: modelEncoded}) 111 c.Check(err, check.IsNil) 112 113 // set it and validate that this is what we was passed to 114 // devicestateRemodel 115 req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data)) 116 c.Assert(err, check.IsNil) 117 rsp := s.asyncReq(c, req, nil) 118 c.Assert(rsp.Status, check.Equals, 202) 119 c.Check(devicestateRemodelGotModel, check.DeepEquals, newModel) 120 121 st.Lock() 122 defer st.Unlock() 123 chg := st.Change(rsp.Change) 124 c.Assert(chg, check.NotNil) 125 126 c.Assert(st.Changes(), check.HasLen, 1) 127 chg1 := st.Changes()[0] 128 c.Assert(chg, check.DeepEquals, chg1) 129 c.Assert(chg.Kind(), check.Equals, "remodel") 130 c.Assert(chg.Err(), check.IsNil) 131 132 c.Assert(soon, check.Equals, 1) 133 } 134 135 func (s *modelSuite) TestGetModelNoModelAssertion(c *check.C) { 136 137 d := s.daemonWithOverlordMockAndStore(c) 138 hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner()) 139 c.Assert(err, check.IsNil) 140 deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil) 141 c.Assert(err, check.IsNil) 142 d.Overlord().AddManager(deviceMgr) 143 144 req, err := http.NewRequest("GET", "/v2/model", nil) 145 c.Assert(err, check.IsNil) 146 rspe := s.errorReq(c, req, nil) 147 c.Assert(rspe.Status, check.Equals, 404) 148 c.Assert(rspe.Kind, check.Equals, client.ErrorKindAssertionNotFound) 149 c.Assert(rspe.Value, check.Equals, "model") 150 c.Assert(rspe.Message, check.Equals, "no model assertion yet") 151 } 152 153 func (s *modelSuite) TestGetModelHasModelAssertion(c *check.C) { 154 // make a model assertion 155 theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults) 156 157 // model assertion setup 158 d := s.daemonWithOverlordMockAndStore(c) 159 hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner()) 160 c.Assert(err, check.IsNil) 161 deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil) 162 c.Assert(err, check.IsNil) 163 d.Overlord().AddManager(deviceMgr) 164 st := d.Overlord().State() 165 st.Lock() 166 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey("")) 167 assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...) 168 s.mockModel(c, st, theModel) 169 st.Unlock() 170 171 // make a new get request to the model endpoint 172 req, err := http.NewRequest("GET", "/v2/model", nil) 173 c.Assert(err, check.IsNil) 174 rec := httptest.NewRecorder() 175 s.req(c, req, nil).ServeHTTP(rec, req) 176 177 // check that we get an assertion response 178 c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) 179 c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion") 180 181 // check that there is only one assertion 182 dec := asserts.NewDecoder(rec.Body) 183 m, err := dec.Decode() 184 c.Assert(err, check.IsNil) 185 _, err = dec.Decode() 186 c.Assert(err, check.Equals, io.EOF) 187 188 // check that one of the assertion keys matches what's in the model we 189 // provided 190 c.Check(m.Type(), check.Equals, asserts.ModelType) 191 arch := m.Header("architecture") 192 c.Assert(arch, check.FitsTypeOf, "") 193 c.Assert(arch.(string), check.Equals, "amd64") 194 } 195 196 func (s *modelSuite) TestGetModelJSONHasModelAssertion(c *check.C) { 197 // make a model assertion 198 theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults) 199 200 // model assertion setup 201 d := s.daemonWithOverlordMockAndStore(c) 202 hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner()) 203 c.Assert(err, check.IsNil) 204 deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil) 205 c.Assert(err, check.IsNil) 206 d.Overlord().AddManager(deviceMgr) 207 st := d.Overlord().State() 208 st.Lock() 209 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey("")) 210 assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...) 211 s.mockModel(c, st, theModel) 212 st.Unlock() 213 214 // make a new get request to the model endpoint with json as true 215 req, err := http.NewRequest("GET", "/v2/model?json=true", nil) 216 c.Assert(err, check.IsNil) 217 rsp := s.syncReq(c, req, nil) 218 // get the body and try to unmarshal into modelAssertJSON 219 c.Assert(rsp.Result, check.FitsTypeOf, daemon.ModelAssertJSON{}) 220 221 jsonResponse := rsp.Result.(daemon.ModelAssertJSON) 222 223 // get the architecture key from the headers 224 arch, ok := jsonResponse.Headers["architecture"] 225 c.Assert(ok, check.Equals, true) 226 227 // ensure that the architecture key is what we set in the model defaults 228 c.Assert(arch, check.FitsTypeOf, "") 229 c.Assert(arch.(string), check.Equals, "amd64") 230 } 231 232 func (s *modelSuite) TestGetModelNoSerialAssertion(c *check.C) { 233 234 d := s.daemonWithOverlordMockAndStore(c) 235 hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner()) 236 c.Assert(err, check.IsNil) 237 deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil) 238 c.Assert(err, check.IsNil) 239 d.Overlord().AddManager(deviceMgr) 240 241 req, err := http.NewRequest("GET", "/v2/model/serial", nil) 242 c.Assert(err, check.IsNil) 243 rspe := s.errorReq(c, req, nil) 244 c.Assert(rspe.Status, check.Equals, 404) 245 c.Assert(rspe.Kind, check.Equals, client.ErrorKindAssertionNotFound) 246 c.Assert(rspe.Value, check.Equals, "serial") 247 c.Assert(rspe.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 }