github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_validate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 "fmt" 24 "net/http" 25 "net/url" 26 "sort" 27 "strings" 28 29 "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/assertstest" 33 "github.com/snapcore/snapd/asserts/snapasserts" 34 "github.com/snapcore/snapd/daemon" 35 "github.com/snapcore/snapd/overlord/assertstate" 36 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 37 "github.com/snapcore/snapd/overlord/auth" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/naming" 43 ) 44 45 var _ = check.Suite(&apiValidationSetsSuite{}) 46 47 type apiValidationSetsSuite struct { 48 apiBaseSuite 49 50 storeSigning *assertstest.StoreStack 51 dev1Signing *assertstest.SigningDB 52 dev1acct *asserts.Account 53 acct1Key *asserts.AccountKey 54 mockSeqFormingAssertionFn func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) 55 } 56 57 type byName []*snapasserts.InstalledSnap 58 59 func (b byName) Len() int { return len(b) } 60 func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 61 func (b byName) Less(i, j int) bool { 62 return b[i].SnapName() < b[j].SnapName() 63 } 64 65 func (s *apiValidationSetsSuite) SetUpTest(c *check.C) { 66 s.apiBaseSuite.SetUpTest(c) 67 d := s.daemon(c) 68 69 restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 1) 70 s.AddCleanup(restore) 71 72 s.mockSeqFormingAssertionFn = nil 73 74 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 75 76 st := d.Overlord().State() 77 st.Lock() 78 snapstate.ReplaceStore(st, s) 79 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 80 st.Unlock() 81 82 s.dev1acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "") 83 c.Assert(s.storeSigning.Add(s.dev1acct), check.IsNil) 84 85 // developer signing 86 dev1PrivKey, _ := assertstest.GenerateKey(752) 87 s.acct1Key = assertstest.NewAccountKey(s.storeSigning, s.dev1acct, nil, dev1PrivKey.PublicKey(), "") 88 89 s.dev1Signing = assertstest.NewSigningDB(s.dev1acct.AccountID(), dev1PrivKey) 90 c.Assert(s.storeSigning.Add(s.acct1Key), check.IsNil) 91 92 d.Overlord().Loop() 93 s.AddCleanup(func() { d.Overlord().Stop() }) 94 } 95 96 func (s *apiValidationSetsSuite) mockValidationSetsTracking(st *state.State) { 97 st.Set("validation-sets", map[string]interface{}{ 98 fmt.Sprintf("%s/foo", s.dev1acct.AccountID()): map[string]interface{}{ 99 "account-id": s.dev1acct.AccountID(), 100 "name": "foo", 101 "mode": assertstate.Enforce, 102 "pinned-at": 9, 103 "current": 99, 104 }, 105 fmt.Sprintf("%s/baz", s.dev1acct.AccountID()): map[string]interface{}{ 106 "account-id": s.dev1acct.AccountID(), 107 "name": "baz", 108 "mode": assertstate.Monitor, 109 "pinned-at": 0, 110 "current": 2, 111 }, 112 }) 113 } 114 115 func (s *apiValidationSetsSuite) mockAssert(c *check.C, name, sequence string) asserts.Assertion { 116 snaps := []interface{}{map[string]interface{}{ 117 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 118 "name": "snap-b", 119 "presence": "required", 120 "revision": "1", 121 }} 122 headers := map[string]interface{}{ 123 "authority-id": s.dev1acct.AccountID(), 124 "account-id": s.dev1acct.AccountID(), 125 "name": name, 126 "series": "16", 127 "sequence": sequence, 128 "revision": "5", 129 "timestamp": "2030-11-06T09:16:26Z", 130 "snaps": snaps, 131 } 132 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 133 c.Assert(err, check.IsNil) 134 return vs 135 } 136 137 func (s *apiValidationSetsSuite) SeqFormingAssertion(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 138 s.pokeStateLock() 139 return s.mockSeqFormingAssertionFn(assertType, sequenceKey, sequence, user) 140 } 141 142 func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) { 143 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 144 return nil, &asserts.NotFoundError{ 145 Type: assertType, 146 } 147 } 148 149 st := s.d.Overlord().State() 150 st.Lock() 151 s.mockValidationSetsTracking(st) 152 st.Unlock() 153 154 for i, tc := range []struct { 155 validationSet string 156 // sequence is normally an int, use string for passing invalid ones. 157 sequence string 158 message string 159 status int 160 }{ 161 { 162 validationSet: "abc/Xfoo", 163 message: `invalid name "Xfoo"`, 164 status: 400, 165 }, 166 { 167 validationSet: "Xfoo/bar", 168 message: `invalid account ID "Xfoo"`, 169 status: 400, 170 }, 171 { 172 validationSet: "foo/foo", 173 message: "validation set not found", 174 status: 404, 175 }, 176 { 177 validationSet: "foo/bar", 178 sequence: "1999", 179 message: "validation set not found", 180 status: 404, 181 }, 182 { 183 validationSet: "foo/bar", 184 sequence: "x", 185 message: "invalid sequence argument", 186 status: 400, 187 }, 188 { 189 validationSet: "foo/bar", 190 sequence: "-2", 191 message: "invalid sequence argument: -2", 192 status: 400, 193 }, 194 } { 195 q := url.Values{} 196 if tc.sequence != "" { 197 q.Set("sequence", tc.sequence) 198 } 199 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil) 200 c.Assert(err, check.IsNil) 201 rsp := s.errorReq(c, req, nil) 202 c.Check(rsp.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 203 c.Check(rsp.ErrorResult().Message, check.Matches, tc.message) 204 } 205 } 206 207 func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) { 208 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 209 c.Assert(err, check.IsNil) 210 211 rsp := s.syncReq(c, req, nil) 212 c.Assert(rsp.Status, check.Equals, 200) 213 res := rsp.Result.([]daemon.ValidationSetResult) 214 c.Check(res, check.HasLen, 0) 215 } 216 217 func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) { 218 st := s.d.Overlord().State() 219 st.Lock() 220 s.mockValidationSetsTracking(st) 221 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 222 as := s.mockAssert(c, "foo", "9") 223 err := assertstate.Add(st, as) 224 c.Check(err, check.IsNil) 225 as = s.mockAssert(c, "baz", "2") 226 err = assertstate.Add(st, as) 227 st.Unlock() 228 c.Assert(err, check.IsNil) 229 230 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 231 c.Assert(err, check.IsNil) 232 rsp := s.syncReq(c, req, nil) 233 c.Assert(rsp.Status, check.Equals, 200) 234 res := rsp.Result.([]daemon.ValidationSetResult) 235 c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{ 236 { 237 AccountID: s.dev1acct.AccountID(), 238 Name: "baz", 239 Mode: "monitor", 240 Sequence: 2, 241 Valid: false, 242 }, 243 { 244 AccountID: s.dev1acct.AccountID(), 245 Name: "foo", 246 PinnedAt: 9, 247 Mode: "enforce", 248 Sequence: 99, 249 Valid: false, 250 }, 251 }) 252 } 253 254 func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) { 255 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 256 return nil, &asserts.NotFoundError{ 257 Type: assertType, 258 } 259 } 260 261 st := s.d.Overlord().State() 262 st.Lock() 263 as := s.mockAssert(c, "baz", "2") 264 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 265 s.mockValidationSetsTracking(st) 266 st.Unlock() 267 268 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/baz", s.dev1acct.AccountID()), nil) 269 c.Assert(err, check.IsNil) 270 271 rsp := s.syncReq(c, req, nil) 272 c.Assert(rsp.Status, check.Equals, 200) 273 res := rsp.Result.(daemon.ValidationSetResult) 274 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 275 AccountID: s.dev1acct.AccountID(), 276 Name: "baz", 277 Mode: "monitor", 278 Sequence: 2, 279 Valid: false, 280 }) 281 } 282 283 func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) { 284 q := url.Values{} 285 q.Set("sequence", "9") 286 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/foo?%s", s.dev1acct.AccountID(), q.Encode()), nil) 287 c.Assert(err, check.IsNil) 288 289 st := s.d.Overlord().State() 290 st.Lock() 291 as := s.mockAssert(c, "foo", "9") 292 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 293 s.mockValidationSetsTracking(st) 294 st.Unlock() 295 c.Assert(err, check.IsNil) 296 297 rsp := s.syncReq(c, req, nil) 298 c.Assert(rsp.Status, check.Equals, 200) 299 res := rsp.Result.(daemon.ValidationSetResult) 300 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 301 AccountID: s.dev1acct.AccountID(), 302 Name: "foo", 303 PinnedAt: 9, 304 Mode: "enforce", 305 Sequence: 99, 306 Valid: false, 307 }) 308 } 309 310 func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) { 311 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 312 return nil, &asserts.NotFoundError{ 313 Type: assertType, 314 } 315 } 316 317 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 318 c.Assert(err, check.IsNil) 319 320 st := s.d.Overlord().State() 321 st.Lock() 322 s.mockValidationSetsTracking(st) 323 st.Unlock() 324 325 rsp := s.errorReq(c, req, nil) 326 c.Assert(rsp.Status, check.Equals, 404) 327 res := rsp.Result.(*daemon.ErrorResult) 328 c.Assert(res, check.NotNil) 329 c.Check(string(res.Kind), check.Equals, "validation-set-not-found") 330 c.Check(res.Value, check.DeepEquals, map[string]interface{}{ 331 "account-id": "foo", 332 "name": "other", 333 }) 334 } 335 336 var validationSetAssertion = []byte("type: validation-set\n" + 337 "format: 1\n" + 338 "authority-id: foo\n" + 339 "account-id: foo\n" + 340 "name: other\n" + 341 "sequence: 2\n" + 342 "revision: 5\n" + 343 "series: 16\n" + 344 "snaps:\n" + 345 " -\n" + 346 " id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz\n" + 347 " name: snap-b\n" + 348 " presence: required\n" + 349 " revision: 1\n" + 350 "timestamp: 2020-11-06T09:16:26Z\n" + 351 "sign-key-sha3-384: 7bbncP0c4RcufwReeiylCe0S7IMCn-tHLNSCgeOVmV3K-7_MzpAHgJDYeOjldefE\n\n" + 352 "AXNpZw==") 353 354 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemote(c *check.C) { 355 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 356 c.Assert(assertType, check.NotNil) 357 c.Assert(assertType.Name, check.Equals, "validation-set") 358 // no sequence number element, querying the latest 359 c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"}) 360 c.Assert(sequence, check.Equals, 0) 361 as, err := asserts.Decode(validationSetAssertion) 362 c.Assert(err, check.IsNil) 363 // sanity 364 c.Assert(as.Type().Name, check.Equals, "validation-set") 365 return as, nil 366 } 367 368 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error { 369 c.Assert(vsets, check.NotNil) 370 sort.Sort(byName(snaps)) 371 c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{ 372 { 373 SnapRef: naming.NewSnapRef("snap-a", "snapaid"), 374 Revision: snap.R(2), 375 }, 376 { 377 SnapRef: naming.NewSnapRef("snap-b", "snapbid"), 378 Revision: snap.R(4), 379 }, 380 }) 381 // nil indicates successful validation 382 return nil 383 }) 384 defer restore() 385 386 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 387 c.Assert(err, check.IsNil) 388 389 st := s.d.Overlord().State() 390 st.Lock() 391 s.mockValidationSetsTracking(st) 392 393 snapstate.Set(st, "snap-a", &snapstate.SnapState{ 394 Active: true, 395 Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(2), SnapID: "snapaid"}}, 396 Current: snap.R(2), 397 }) 398 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 399 Active: true, 400 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(4), SnapID: "snapbid"}}, 401 Current: snap.R(4), 402 }) 403 404 st.Unlock() 405 406 rsp := s.syncReq(c, req, nil) 407 c.Assert(rsp.Status, check.Equals, 200) 408 res := rsp.Result.(daemon.ValidationSetResult) 409 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 410 AccountID: "foo", 411 Name: "other", 412 Sequence: 2, 413 Valid: true, 414 }) 415 } 416 417 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteValidationFails(c *check.C) { 418 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 419 as, err := asserts.Decode(validationSetAssertion) 420 c.Assert(err, check.IsNil) 421 return as, nil 422 } 423 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error { 424 return &snapasserts.ValidationSetsValidationError{} 425 }) 426 defer restore() 427 428 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 429 c.Assert(err, check.IsNil) 430 rsp := s.syncReq(c, req, nil) 431 c.Assert(rsp.Status, check.Equals, 200) 432 433 res := rsp.Result.(daemon.ValidationSetResult) 434 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 435 AccountID: "foo", 436 Name: "other", 437 Sequence: 2, 438 Valid: false, 439 }) 440 } 441 442 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteRealValidation(c *check.C) { 443 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 444 as, err := asserts.Decode(validationSetAssertion) 445 c.Assert(err, check.IsNil) 446 return as, nil 447 } 448 449 st := s.d.Overlord().State() 450 451 for _, tc := range []struct { 452 revision snap.Revision 453 expectedValidationStatus bool 454 }{ 455 // required at revision 1 per validationSetAssertion, so it's valid 456 {snap.R(1), true}, 457 // but revision 2 is not valid 458 {snap.R(2), false}, 459 } { 460 st.Lock() 461 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 462 Active: true, 463 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: tc.revision, SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 464 Current: tc.revision, 465 }) 466 st.Unlock() 467 468 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 469 c.Assert(err, check.IsNil) 470 rsp := s.syncReq(c, req, nil) 471 c.Assert(rsp.Status, check.Equals, 200) 472 473 res := rsp.Result.(daemon.ValidationSetResult) 474 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 475 AccountID: "foo", 476 Name: "other", 477 Sequence: 2, 478 Valid: tc.expectedValidationStatus, 479 }) 480 } 481 } 482 483 func (s *apiValidationSetsSuite) TestGetValidationSetSpecificSequenceFromRemote(c *check.C) { 484 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 485 c.Assert(assertType, check.NotNil) 486 c.Assert(assertType.Name, check.Equals, "validation-set") 487 c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"}) 488 c.Assert(sequence, check.Equals, 2) 489 as, err := asserts.Decode(validationSetAssertion) 490 c.Assert(err, check.IsNil) 491 return as, nil 492 } 493 494 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error { 495 c.Assert(vsets, check.NotNil) 496 sort.Sort(byName(snaps)) 497 c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{ 498 { 499 SnapRef: naming.NewSnapRef("snap-a", "snapaid"), 500 Revision: snap.R(33), 501 }, 502 }) 503 // nil indicates successful validation 504 return nil 505 }) 506 defer restore() 507 508 q := url.Values{} 509 q.Set("sequence", "2") 510 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other?"+q.Encode(), nil) 511 c.Assert(err, check.IsNil) 512 513 st := s.d.Overlord().State() 514 st.Lock() 515 s.mockValidationSetsTracking(st) 516 517 snapstate.Set(st, "snap-a", &snapstate.SnapState{ 518 Active: true, 519 Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(33), SnapID: "snapaid"}}, 520 Current: snap.R(33), 521 }) 522 523 st.Unlock() 524 525 rsp := s.syncReq(c, req, nil) 526 c.Assert(rsp.Status, check.Equals, 200) 527 res := rsp.Result.(daemon.ValidationSetResult) 528 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 529 AccountID: "foo", 530 Name: "other", 531 Sequence: 2, 532 Valid: true, 533 }) 534 } 535 536 func (s *apiValidationSetsSuite) TestGetValidationSetFromRemoteFallbackToLocalAssertion(c *check.C) { 537 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 538 // not found in the store 539 return nil, &asserts.NotFoundError{ 540 Type: assertType, 541 } 542 } 543 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error { 544 // nil indicates successful validation 545 return nil 546 }) 547 defer restore() 548 549 st := s.d.Overlord().State() 550 st.Lock() 551 // assertion available in the local db (from snap ack) 552 vs := s.mockAssert(c, "bar", "2") 553 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs) 554 st.Unlock() 555 556 q := url.Values{} 557 q.Set("sequence", "2") 558 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/bar?%s", s.dev1acct.AccountID(), q.Encode()), nil) 559 c.Assert(err, check.IsNil) 560 561 rsp := s.syncReq(c, req, nil) 562 c.Assert(rsp.Status, check.Equals, 200) 563 res := rsp.Result.(daemon.ValidationSetResult) 564 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 565 AccountID: s.dev1acct.AccountID(), 566 Name: "bar", 567 Sequence: 2, 568 Valid: true, 569 }) 570 } 571 572 func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) { 573 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 574 return nil, &asserts.NotFoundError{ 575 Type: assertType, 576 } 577 } 578 579 q := url.Values{} 580 q.Set("sequence", "333") 581 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil) 582 c.Assert(err, check.IsNil) 583 584 st := s.d.Overlord().State() 585 st.Lock() 586 s.mockValidationSetsTracking(st) 587 st.Unlock() 588 589 rsp := s.errorReq(c, req, nil) 590 c.Assert(rsp.Status, check.Equals, 404) 591 res := rsp.Result.(*daemon.ErrorResult) 592 c.Assert(res, check.NotNil) 593 c.Check(string(res.Kind), check.Equals, "validation-set-not-found") 594 c.Check(res.Value, check.DeepEquals, map[string]interface{}{ 595 "account-id": "foo", 596 "name": "bar", 597 "sequence": 333, 598 }) 599 } 600 601 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) { 602 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 603 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 604 c.Assert(name, check.Equals, "bar") 605 c.Assert(sequence, check.Equals, 99) 606 c.Assert(pinned, check.Equals, true) 607 c.Assert(opts, check.NotNil) 608 c.Check(opts.AllowLocalFallback, check.Equals, true) 609 610 db := assertstate.DB(st) 611 headers, err := asserts.HeadersFromPrimaryKey(asserts.ValidationSetType, []string{release.Series, accountID, name, fmt.Sprintf("%d", sequence)}) 612 c.Assert(err, check.IsNil) 613 // validation set assertion available locally 614 vs, err := db.Find(asserts.ValidationSetType, headers) 615 c.Assert(err, check.IsNil) 616 return vs.(*asserts.ValidationSet), true, nil 617 }) 618 defer restore() 619 620 st := s.d.Overlord().State() 621 622 st.Lock() 623 vs := s.mockAssert(c, "bar", "99") 624 // add validation set assertion to the local db 625 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs) 626 st.Unlock() 627 628 body := `{"action":"apply","mode":"monitor", "sequence":99}` 629 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 630 c.Assert(err, check.IsNil) 631 632 rsp := s.syncReq(c, req, nil) 633 c.Assert(rsp.Status, check.Equals, 200) 634 635 var tr assertstate.ValidationSetTracking 636 637 // verify tracking information 638 st.Lock() 639 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 640 st.Unlock() 641 c.Assert(err, check.IsNil) 642 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 643 Mode: assertstate.Monitor, 644 AccountID: s.dev1acct.AccountID(), 645 Name: "bar", 646 PinnedAt: 99, 647 Current: 99, 648 LocalOnly: true, 649 }) 650 } 651 652 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedUnresolved(c *check.C) { 653 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 654 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 655 c.Assert(name, check.Equals, "bar") 656 c.Assert(sequence, check.Equals, 99) 657 c.Assert(pinned, check.Equals, true) 658 659 snaps := []interface{}{map[string]interface{}{ 660 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 661 "name": "snap-b", 662 "presence": "required", 663 "revision": "1", 664 }} 665 headers := map[string]interface{}{ 666 "authority-id": s.dev1acct.AccountID(), 667 "account-id": s.dev1acct.AccountID(), 668 "name": "bar", 669 "series": "16", 670 "sequence": "99", 671 "revision": "5", 672 "timestamp": "2030-11-06T09:16:26Z", 673 "snaps": snaps, 674 } 675 // validation set assertion coming from the store 676 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 677 c.Assert(err, check.IsNil) 678 return vs.(*asserts.ValidationSet), false, nil 679 }) 680 defer restore() 681 682 st := s.d.Overlord().State() 683 684 st.Lock() 685 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 686 st.Unlock() 687 688 body := `{"action":"apply","mode":"monitor", "sequence":99}` 689 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 690 c.Assert(err, check.IsNil) 691 692 rsp := s.syncReq(c, req, nil) 693 c.Assert(rsp.Status, check.Equals, 200) 694 695 var tr assertstate.ValidationSetTracking 696 697 // verify tracking information 698 st.Lock() 699 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 700 st.Unlock() 701 c.Assert(err, check.IsNil) 702 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 703 Mode: assertstate.Monitor, 704 AccountID: s.dev1acct.AccountID(), 705 Name: "bar", 706 PinnedAt: 99, 707 Current: 99, 708 }) 709 } 710 711 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeUnpinnedRefreshed(c *check.C) { 712 snaps := []interface{}{map[string]interface{}{ 713 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 714 "name": "snap-b", 715 "presence": "required", 716 "revision": "1", 717 }} 718 719 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 720 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 721 c.Assert(name, check.Equals, "bar") 722 c.Assert(sequence, check.Equals, 0) 723 c.Assert(pinned, check.Equals, false) 724 725 // new sequence 726 headers := map[string]interface{}{ 727 "authority-id": s.dev1acct.AccountID(), 728 "account-id": s.dev1acct.AccountID(), 729 "name": "bar", 730 "series": "16", 731 "sequence": "2", 732 "revision": "1", 733 "timestamp": "2030-11-06T09:16:26Z", 734 "snaps": snaps, 735 } 736 // updated validation set assertion coming from the store 737 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 738 c.Assert(err, check.IsNil) 739 return vs.(*asserts.ValidationSet), false, nil 740 }) 741 defer restore() 742 743 st := s.d.Overlord().State() 744 745 st.Lock() 746 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 747 st.Unlock() 748 749 headers := map[string]interface{}{ 750 "authority-id": s.dev1acct.AccountID(), 751 "account-id": s.dev1acct.AccountID(), 752 "name": "bar", 753 "series": "16", 754 "sequence": "1", 755 "revision": "1", 756 "timestamp": "2030-11-06T09:16:26Z", 757 "snaps": snaps, 758 } 759 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 760 c.Assert(err, check.IsNil) 761 762 st.Lock() 763 // add validation set assertion to the local db 764 c.Assert(assertstate.Add(st, vs), check.IsNil) 765 st.Unlock() 766 767 body := `{"action":"apply","mode":"monitor"}` 768 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 769 c.Assert(err, check.IsNil) 770 771 rsp := s.syncReq(c, req, nil) 772 c.Assert(rsp.Status, check.Equals, 200) 773 774 var tr assertstate.ValidationSetTracking 775 776 // verify tracking information 777 st.Lock() 778 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 779 st.Unlock() 780 c.Assert(err, check.IsNil) 781 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 782 Mode: assertstate.Monitor, 783 AccountID: s.dev1acct.AccountID(), 784 Name: "bar", 785 Current: 2, 786 }) 787 } 788 789 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) { 790 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 791 return nil, false, fmt.Errorf("boom") 792 }) 793 defer restore() 794 795 body := `{"action":"apply","mode":"monitor"}` 796 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 797 c.Assert(err, check.IsNil) 798 799 rsp := s.errorReq(c, req, nil) 800 c.Assert(rsp.Status, check.Equals, 400) 801 c.Check(rsp.ErrorResult().Message, check.Equals, fmt.Sprintf(`cannot get validation set assertion for %s/bar: boom`, s.dev1acct.AccountID())) 802 } 803 804 func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) { 805 st := s.d.Overlord().State() 806 807 for i, sequence := range []int{0, 9} { 808 st.Lock() 809 s.mockValidationSetsTracking(st) 810 st.Unlock() 811 812 var body string 813 if sequence != 0 { 814 body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence) 815 } else { 816 body = fmt.Sprintf(`{"action":"forget"}`) 817 } 818 819 var tr assertstate.ValidationSetTracking 820 821 st.Lock() 822 // sanity, it exists before removing 823 err := assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 824 st.Unlock() 825 c.Assert(err, check.IsNil) 826 c.Check(tr.AccountID, check.Equals, s.dev1acct.AccountID()) 827 c.Check(tr.Name, check.Equals, "foo") 828 829 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 830 c.Assert(err, check.IsNil) 831 rsp := s.syncReq(c, req, nil) 832 c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i)) 833 834 // after forget it's removed 835 st.Lock() 836 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 837 st.Unlock() 838 c.Assert(err, check.Equals, state.ErrNoState) 839 840 // and forget again fails 841 req, err = http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 842 c.Assert(err, check.IsNil) 843 rsp = s.errorReq(c, req, nil) 844 c.Assert(rsp.Status, check.Equals, 404, check.Commentf("case #%d", i)) 845 } 846 } 847 848 func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) { 849 st := s.d.Overlord().State() 850 st.Lock() 851 s.mockValidationSetsTracking(st) 852 st.Unlock() 853 854 for i, tc := range []struct { 855 validationSet string 856 mode string 857 // sequence is normally an int, use string for passing invalid ones. 858 sequence string 859 message string 860 status int 861 }{ 862 { 863 validationSet: "0/zzz", 864 mode: "monitor", 865 message: `invalid account ID "0"`, 866 status: 400, 867 }, 868 { 869 validationSet: "Xfoo/bar", 870 mode: "monitor", 871 message: `invalid account ID "Xfoo"`, 872 status: 400, 873 }, 874 { 875 validationSet: "foo/Xabc", 876 mode: "monitor", 877 message: `invalid name "Xabc"`, 878 status: 400, 879 }, 880 { 881 validationSet: "foo/bar", 882 sequence: "x", 883 message: "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value", 884 status: 400, 885 }, 886 { 887 validationSet: "foo/bar", 888 mode: "bad", 889 message: `invalid mode "bad"`, 890 status: 400, 891 }, 892 // XXX: enable when enforcing is implemented. 893 { 894 validationSet: "foo/bar", 895 mode: "enforce", 896 message: `invalid mode "enforce"`, 897 status: 400, 898 }, 899 { 900 validationSet: "foo/bar", 901 sequence: "-1", 902 mode: "monitor", 903 message: `invalid sequence argument: -1`, 904 status: 400, 905 }, 906 } { 907 var body string 908 if tc.sequence != "" { 909 body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence) 910 } else { 911 body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode) 912 } 913 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body)) 914 c.Assert(err, check.IsNil) 915 rsp := s.errorReq(c, req, nil) 916 c.Check(rsp.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 917 c.Check(rsp.ErrorResult().Message, check.Matches, tc.message) 918 } 919 } 920 921 func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) { 922 body := fmt.Sprintf(`{"action":"baz","mode":"monitor"}`) 923 924 req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body)) 925 c.Assert(err, check.IsNil) 926 927 rsp := s.errorReq(c, req, nil) 928 c.Check(rsp.Status, check.Equals, 400) 929 c.Check(rsp.ErrorResult().Message, check.Matches, `unsupported action "baz"`) 930 }