gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/storecontext/context_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-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 storecontext_test 21 22 import ( 23 "errors" 24 "net/url" 25 "os" 26 "strings" 27 "testing" 28 "time" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/asserts/sysdb" 34 "github.com/snapcore/snapd/overlord/auth" 35 "github.com/snapcore/snapd/overlord/configstate/config" 36 "github.com/snapcore/snapd/overlord/state" 37 "github.com/snapcore/snapd/overlord/storecontext" 38 "github.com/snapcore/snapd/store" 39 ) 40 41 func Test(t *testing.T) { TestingT(t) } 42 43 type storeCtxSuite struct { 44 state *state.State 45 46 defURL *url.URL 47 } 48 49 var _ = Suite(&storeCtxSuite{}) 50 51 func (s *storeCtxSuite) SetupSuite(c *C) { 52 var err error 53 s.defURL, err = url.Parse("http://store") 54 c.Assert(err, IsNil) 55 } 56 57 func (s *storeCtxSuite) SetUpTest(c *C) { 58 s.state = state.New(nil) 59 } 60 61 func (s *storeCtxSuite) TestUpdateUserAuth(c *C) { 62 s.state.Lock() 63 user, _ := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"}) 64 s.state.Unlock() 65 66 newDischarges := []string{"updated-discharge"} 67 68 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 69 user, err := storeCtx.UpdateUserAuth(user, newDischarges) 70 c.Check(err, IsNil) 71 72 s.state.Lock() 73 userFromState, err := auth.User(s.state, user.ID) 74 s.state.Unlock() 75 c.Check(err, IsNil) 76 c.Check(userFromState, DeepEquals, user) 77 c.Check(userFromState.Discharges, IsNil) 78 c.Check(user.StoreDischarges, DeepEquals, newDischarges) 79 } 80 81 func (s *storeCtxSuite) TestUpdateUserAuthOtherUpdate(c *C) { 82 s.state.Lock() 83 user, _ := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"}) 84 otherUpdateUser := *user 85 otherUpdateUser.Macaroon = "macaroon2" 86 otherUpdateUser.StoreDischarges = []string{"other-discharges"} 87 err := auth.UpdateUser(s.state, &otherUpdateUser) 88 s.state.Unlock() 89 c.Assert(err, IsNil) 90 91 newDischarges := []string{"updated-discharge"} 92 93 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 94 // last discharges win 95 curUser, err := storeCtx.UpdateUserAuth(user, newDischarges) 96 c.Assert(err, IsNil) 97 98 s.state.Lock() 99 userFromState, err := auth.User(s.state, user.ID) 100 s.state.Unlock() 101 c.Check(err, IsNil) 102 c.Check(userFromState, DeepEquals, curUser) 103 c.Check(curUser, DeepEquals, &auth.UserState{ 104 ID: user.ID, 105 Username: "username", 106 Email: "email@test.com", 107 Macaroon: "macaroon2", 108 Discharges: nil, 109 StoreMacaroon: "macaroon", 110 StoreDischarges: newDischarges, 111 }) 112 } 113 114 func (s *storeCtxSuite) TestUpdateUserAuthInvalid(c *C) { 115 s.state.Lock() 116 _, _ = auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"}) 117 s.state.Unlock() 118 119 user := &auth.UserState{ 120 ID: 102, 121 Username: "username", 122 Macaroon: "macaroon", 123 } 124 125 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 126 _, err := storeCtx.UpdateUserAuth(user, nil) 127 c.Assert(err, Equals, auth.ErrInvalidUser) 128 } 129 130 func (s *storeCtxSuite) TestDeviceForNonExistent(c *C) { 131 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 132 133 device, err := storeCtx.Device() 134 c.Check(err, IsNil) 135 c.Check(device, DeepEquals, &auth.DeviceState{}) 136 } 137 138 func (s *storeCtxSuite) TestDevice(c *C) { 139 device := &auth.DeviceState{Brand: "some-brand"} 140 storeCtx := storecontext.New(s.state, &testBackend{device: device}) 141 142 deviceFromState, err := storeCtx.Device() 143 c.Check(err, IsNil) 144 c.Check(deviceFromState, DeepEquals, device) 145 } 146 147 func (s *storeCtxSuite) TestUpdateDeviceAuth(c *C) { 148 device := &auth.DeviceState{} 149 storeCtx := storecontext.New(s.state, &testBackend{device: device}) 150 151 sessionMacaroon := "the-device-macaroon" 152 device, err := storeCtx.UpdateDeviceAuth(device, sessionMacaroon) 153 c.Check(err, IsNil) 154 155 deviceFromState, err := storeCtx.Device() 156 c.Check(err, IsNil) 157 c.Check(deviceFromState, DeepEquals, device) 158 c.Check(deviceFromState.SessionMacaroon, DeepEquals, sessionMacaroon) 159 } 160 161 func (s *storeCtxSuite) TestUpdateDeviceAuthOtherUpdate(c *C) { 162 device := &auth.DeviceState{} 163 otherUpdateDevice := *device 164 otherUpdateDevice.SessionMacaroon = "other-session-macaroon" 165 otherUpdateDevice.KeyID = "KEYID" 166 167 b := &testBackend{device: &otherUpdateDevice} 168 storeCtx := storecontext.New(s.state, b) 169 170 // the global store refreshing sessions is now serialized 171 // and is a no-op in this case, but we do need not to overwrite 172 // the result of a remodeling (though unlikely as we will mostly avoid 173 // other store operations during it) 174 sessionMacaroon := "the-device-macaroon" 175 curDevice, err := storeCtx.UpdateDeviceAuth(device, sessionMacaroon) 176 c.Assert(err, IsNil) 177 178 c.Check(b.device, DeepEquals, curDevice) 179 c.Check(curDevice, DeepEquals, &auth.DeviceState{ 180 KeyID: "KEYID", 181 SessionMacaroon: "other-session-macaroon", 182 }) 183 } 184 185 func (s *storeCtxSuite) TestStoreParamsFallback(c *C) { 186 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 187 188 storeID, err := storeCtx.StoreID("store-id") 189 c.Assert(err, IsNil) 190 c.Check(storeID, Equals, "store-id") 191 192 proxyStoreID, proxyStoreURL, err := storeCtx.ProxyStoreParams(s.defURL) 193 c.Assert(err, IsNil) 194 c.Check(proxyStoreID, Equals, "") 195 c.Check(proxyStoreURL, Equals, s.defURL) 196 } 197 198 func (s *storeCtxSuite) TestStoreIDFromEnv(c *C) { 199 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 200 201 os.Setenv("UBUNTU_STORE_ID", "env-store-id") 202 defer os.Unsetenv("UBUNTU_STORE_ID") 203 storeID, err := storeCtx.StoreID("") 204 c.Assert(err, IsNil) 205 c.Check(storeID, Equals, "env-store-id") 206 } 207 208 func (s *storeCtxSuite) TestCloudInfo(c *C) { 209 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 210 211 cloud, err := storeCtx.CloudInfo() 212 c.Assert(err, IsNil) 213 c.Check(cloud, IsNil) 214 215 cloudInfo := &auth.CloudInfo{ 216 Name: "aws", 217 Region: "us-east-1", 218 AvailabilityZone: "us-east-1a", 219 } 220 s.state.Lock() 221 defer s.state.Unlock() 222 tr := config.NewTransaction(s.state) 223 tr.Set("core", "cloud", cloudInfo) 224 tr.Commit() 225 226 s.state.Unlock() 227 cloud, err = storeCtx.CloudInfo() 228 s.state.Lock() 229 c.Assert(err, IsNil) 230 c.Check(cloud, DeepEquals, cloudInfo) 231 } 232 233 const ( 234 exModel = `type: model 235 authority-id: my-brand 236 series: 16 237 brand-id: my-brand 238 model: baz-3000 239 architecture: armhf 240 gadget: gadget 241 kernel: kernel 242 store: my-brand-store-id 243 timestamp: 2016-08-20T13:00:00Z 244 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 245 246 AXNpZw=` 247 248 exSerial = `type: serial 249 authority-id: my-brand 250 brand-id: my-brand 251 model: baz-3000 252 serial: 9999 253 device-key: 254 AcbBTQRWhcGAARAAtJGIguK7FhSyRxL/6jvdy0zAgGCjC1xVNFzeF76p5G8BXNEEHZUHK+z8Gr2J 255 inVrpvhJhllf5Ob2dIMH2YQbC9jE1kjbzvuauQGDqk6tNQm0i3KDeHCSPgVN+PFXPwKIiLrh66Po 256 AC7OfR1rFUgCqu0jch0H6Nue0ynvEPiY4dPeXq7mCdpDr5QIAM41L+3hg0OdzvO8HMIGZQpdF6jP 257 7fkkVMROYvHUOJ8kknpKE7FiaNNpH7jK1qNxOYhLeiioX0LYrdmTvdTWHrSKZc82ZmlDjpKc4hUx 258 VtTXMAysw7CzIdREPom/vJklnKLvZt+Wk5AEF5V5YKnuT3pY+fjVMZ56GtTEeO/Er/oLk/n2xUK5 259 fD5DAyW/9z0ygzwTbY5IuWXyDfYneL4nXwWOEgg37Z4+8mTH+ftTz2dl1x1KIlIR2xo0kxf9t8K+ 260 jlr13vwF1+QReMCSUycUsZ2Eep5XhjI+LG7G1bMSGqodZTIOXLkIy6+3iJ8Z/feIHlJ0ELBDyFbl 261 Yy04Sf9LI148vJMsYenonkoWejWdMi8iCUTeaZydHJEUBU/RbNFLjCWa6NIUe9bfZgLiOOZkps54 262 +/AL078ri/tGjo/5UGvezSmwrEoWJyqrJt2M69N2oVDLJcHeo2bUYPtFC2Kfb2je58JrJ+llifdg 263 rAsxbnHXiXyVimUAEQEAAQ== 264 device-key-sha3-384: EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu 265 timestamp: 2016-08-24T21:55:00Z 266 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 267 268 AXNpZw=` 269 270 exDeviceSessionRequest = `type: device-session-request 271 brand-id: my-brand 272 model: baz-3000 273 serial: 9999 274 nonce: @NONCE@ 275 timestamp: @TS@ 276 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 277 278 AXNpZw=` 279 280 exStore = `type: store 281 authority-id: canonical 282 store: foo 283 operator-id: foo-operator 284 url: http://foo.internal 285 timestamp: 2017-11-01T10:00:00Z 286 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 287 288 AXNpZw=` 289 ) 290 291 type testBackend struct { 292 nothing bool 293 noSerial bool 294 device *auth.DeviceState 295 } 296 297 func (b *testBackend) Device() (*auth.DeviceState, error) { 298 freshDevice := auth.DeviceState{} 299 if b.device != nil { 300 freshDevice = *b.device 301 } 302 return &freshDevice, nil 303 } 304 305 func (b *testBackend) SetDevice(d *auth.DeviceState) error { 306 *b.device = *d 307 return nil 308 } 309 310 func (b *testBackend) Model() (*asserts.Model, error) { 311 if b.nothing { 312 return nil, state.ErrNoState 313 } 314 a, err := asserts.Decode([]byte(exModel)) 315 if err != nil { 316 return nil, err 317 } 318 return a.(*asserts.Model), nil 319 } 320 321 func (b *testBackend) Serial() (*asserts.Serial, error) { 322 if b.nothing || b.noSerial { 323 return nil, state.ErrNoState 324 } 325 a, err := asserts.Decode([]byte(exSerial)) 326 if err != nil { 327 return nil, err 328 } 329 return a.(*asserts.Serial), nil 330 } 331 332 func (b *testBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 333 if b.nothing { 334 return nil, state.ErrNoState 335 } 336 337 ex := strings.Replace(exDeviceSessionRequest, "@NONCE@", nonce, 1) 338 ex = strings.Replace(ex, "@TS@", time.Now().Format(time.RFC3339), 1) 339 aReq, err := asserts.Decode([]byte(ex)) 340 if err != nil { 341 return nil, err 342 } 343 344 return aReq.(*asserts.DeviceSessionRequest), nil 345 } 346 347 func (b *testBackend) ProxyStore() (*asserts.Store, error) { 348 if b.nothing { 349 return nil, state.ErrNoState 350 } 351 a, err := asserts.Decode([]byte(exStore)) 352 if err != nil { 353 return nil, err 354 } 355 return a.(*asserts.Store), nil 356 } 357 358 func (s *storeCtxSuite) TestMissingDeviceAssertions(c *C) { 359 // no assertions in state 360 storeCtx := storecontext.New(s.state, &testBackend{nothing: true}) 361 362 _, err := storeCtx.DeviceSessionRequestParams("NONCE") 363 c.Check(err, Equals, store.ErrNoSerial) 364 365 storeID, err := storeCtx.StoreID("fallback") 366 c.Assert(err, IsNil) 367 c.Check(storeID, Equals, "fallback") 368 369 proxyStoreID, proxyStoreURL, err := storeCtx.ProxyStoreParams(s.defURL) 370 c.Assert(err, IsNil) 371 c.Check(proxyStoreID, Equals, "") 372 c.Check(proxyStoreURL, Equals, s.defURL) 373 } 374 375 func (s *storeCtxSuite) TestWithDeviceAssertions(c *C) { 376 // having assertions in state 377 storeCtx := storecontext.New(s.state, &testBackend{}) 378 379 params, err := storeCtx.DeviceSessionRequestParams("NONCE-1") 380 c.Assert(err, IsNil) 381 382 req := params.EncodedRequest() 383 serial := params.EncodedSerial() 384 model := params.EncodedModel() 385 386 c.Check(strings.Contains(req, "nonce: NONCE-1\n"), Equals, true) 387 c.Check(strings.Contains(req, "serial: 9999\n"), Equals, true) 388 389 c.Check(strings.Contains(serial, "model: baz-3000\n"), Equals, true) 390 c.Check(strings.Contains(serial, "serial: 9999\n"), Equals, true) 391 c.Check(strings.Contains(model, "model: baz-3000\n"), Equals, true) 392 c.Check(strings.Contains(model, "serial:\n"), Equals, false) 393 394 // going to be ignored 395 os.Setenv("UBUNTU_STORE_ID", "env-store-id") 396 defer os.Unsetenv("UBUNTU_STORE_ID") 397 storeID, err := storeCtx.StoreID("store-id") 398 c.Assert(err, IsNil) 399 c.Check(storeID, Equals, "my-brand-store-id") 400 401 // proxy store 402 fooURL, err := url.Parse("http://foo.internal") 403 c.Assert(err, IsNil) 404 405 proxyStoreID, proxyStoreURL, err := storeCtx.ProxyStoreParams(s.defURL) 406 c.Assert(err, IsNil) 407 c.Check(proxyStoreID, Equals, "foo") 408 c.Check(proxyStoreURL, DeepEquals, fooURL) 409 } 410 411 func (s *storeCtxSuite) TestWithDeviceAssertionsGenericClassicModel(c *C) { 412 model, err := asserts.Decode([]byte(exModel)) 413 c.Assert(err, IsNil) 414 // (ab)use the example as the generic classic model 415 r := sysdb.MockGenericClassicModel(model.(*asserts.Model)) 416 defer r() 417 // having assertions in state 418 storeCtx := storecontext.New(s.state, &testBackend{}) 419 420 // for the generic classic model we continue to consider the env var 421 os.Setenv("UBUNTU_STORE_ID", "env-store-id") 422 defer os.Unsetenv("UBUNTU_STORE_ID") 423 storeID, err := storeCtx.StoreID("store-id") 424 c.Assert(err, IsNil) 425 c.Check(storeID, Equals, "env-store-id") 426 } 427 428 func (s *storeCtxSuite) TestWithDeviceAssertionsGenericClassicModelNoEnvVar(c *C) { 429 model, err := asserts.Decode([]byte(exModel)) 430 c.Assert(err, IsNil) 431 // (ab)use the example as the generic classic model 432 r := sysdb.MockGenericClassicModel(model.(*asserts.Model)) 433 defer r() 434 // having assertions in state 435 storeCtx := storecontext.New(s.state, &testBackend{}) 436 437 // for the generic classic model we continue to consider the env var 438 // but when the env var is unset we don't do anything wrong. 439 os.Unsetenv("UBUNTU_STORE_ID") 440 storeID, err := storeCtx.StoreID("store-id") 441 c.Assert(err, IsNil) 442 c.Check(storeID, Equals, "store-id") 443 } 444 445 type testFailingDeviceSessionRequestSigner struct{} 446 447 func (srqs testFailingDeviceSessionRequestSigner) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 448 return nil, errors.New("boom") 449 } 450 451 func (s *storeCtxSuite) TestComposable(c *C) { 452 b := &testBackend{} 453 bNoSerial := &testBackend{noSerial: true} 454 455 storeCtx := storecontext.NewComposed(s.state, b, bNoSerial, b) 456 457 params, err := storeCtx.DeviceSessionRequestParams("NONCE-1") 458 c.Assert(err, IsNil) 459 460 req := params.EncodedRequest() 461 c.Check(strings.Contains(req, "nonce: NONCE-1\n"), Equals, true) 462 c.Check(strings.Contains(req, "serial: 9999\n"), Equals, true) 463 464 storeCtx = storecontext.NewComposed(s.state, bNoSerial, b, b) 465 params, err = storeCtx.DeviceSessionRequestParams("NONCE-1") 466 c.Assert(err, Equals, store.ErrNoSerial) 467 468 srqs := testFailingDeviceSessionRequestSigner{} 469 storeCtx = storecontext.NewComposed(s.state, b, srqs, b) 470 params, err = storeCtx.DeviceSessionRequestParams("NONCE-1") 471 c.Assert(err, ErrorMatches, "boom") 472 }