github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 s.expectAuthenticatedAccess() 70 71 restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 1) 72 s.AddCleanup(restore) 73 74 s.mockSeqFormingAssertionFn = nil 75 76 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 77 78 st := d.Overlord().State() 79 st.Lock() 80 snapstate.ReplaceStore(st, s) 81 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 82 st.Unlock() 83 84 s.dev1acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "") 85 c.Assert(s.storeSigning.Add(s.dev1acct), check.IsNil) 86 87 // developer signing 88 dev1PrivKey, _ := assertstest.GenerateKey(752) 89 s.acct1Key = assertstest.NewAccountKey(s.storeSigning, s.dev1acct, nil, dev1PrivKey.PublicKey(), "") 90 91 s.dev1Signing = assertstest.NewSigningDB(s.dev1acct.AccountID(), dev1PrivKey) 92 c.Assert(s.storeSigning.Add(s.acct1Key), check.IsNil) 93 94 d.Overlord().Loop() 95 s.AddCleanup(func() { d.Overlord().Stop() }) 96 } 97 98 func (s *apiValidationSetsSuite) mockValidationSetsTracking(st *state.State) { 99 st.Set("validation-sets", map[string]interface{}{ 100 fmt.Sprintf("%s/foo", s.dev1acct.AccountID()): map[string]interface{}{ 101 "account-id": s.dev1acct.AccountID(), 102 "name": "foo", 103 "mode": assertstate.Enforce, 104 "pinned-at": 9, 105 "current": 99, 106 }, 107 fmt.Sprintf("%s/baz", s.dev1acct.AccountID()): map[string]interface{}{ 108 "account-id": s.dev1acct.AccountID(), 109 "name": "baz", 110 "mode": assertstate.Monitor, 111 "pinned-at": 0, 112 "current": 2, 113 }, 114 }) 115 } 116 117 func (s *apiValidationSetsSuite) mockAssert(c *check.C, name, sequence string) asserts.Assertion { 118 snaps := []interface{}{map[string]interface{}{ 119 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 120 "name": "snap-b", 121 "presence": "required", 122 "revision": "1", 123 }} 124 headers := map[string]interface{}{ 125 "authority-id": s.dev1acct.AccountID(), 126 "account-id": s.dev1acct.AccountID(), 127 "name": name, 128 "series": "16", 129 "sequence": sequence, 130 "revision": "5", 131 "timestamp": "2030-11-06T09:16:26Z", 132 "snaps": snaps, 133 } 134 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 135 c.Assert(err, check.IsNil) 136 return vs 137 } 138 139 func (s *apiValidationSetsSuite) SeqFormingAssertion(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 140 s.pokeStateLock() 141 return s.mockSeqFormingAssertionFn(assertType, sequenceKey, sequence, user) 142 } 143 144 func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) { 145 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 146 return nil, &asserts.NotFoundError{ 147 Type: assertType, 148 } 149 } 150 151 st := s.d.Overlord().State() 152 st.Lock() 153 s.mockValidationSetsTracking(st) 154 st.Unlock() 155 156 for i, tc := range []struct { 157 validationSet string 158 // sequence is normally an int, use string for passing invalid ones. 159 sequence string 160 message string 161 status int 162 }{ 163 { 164 validationSet: "abc/Xfoo", 165 message: `invalid name "Xfoo"`, 166 status: 400, 167 }, 168 { 169 validationSet: "Xfoo/bar", 170 message: `invalid account ID "Xfoo"`, 171 status: 400, 172 }, 173 { 174 validationSet: "foo/foo", 175 message: "validation set not found", 176 status: 404, 177 }, 178 { 179 validationSet: "foo/bar", 180 sequence: "1999", 181 message: "validation set not found", 182 status: 404, 183 }, 184 { 185 validationSet: "foo/bar", 186 sequence: "x", 187 message: "invalid sequence argument", 188 status: 400, 189 }, 190 { 191 validationSet: "foo/bar", 192 sequence: "-2", 193 message: "invalid sequence argument: -2", 194 status: 400, 195 }, 196 } { 197 q := url.Values{} 198 if tc.sequence != "" { 199 q.Set("sequence", tc.sequence) 200 } 201 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil) 202 c.Assert(err, check.IsNil) 203 rspe := s.errorReq(c, req, nil) 204 c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 205 c.Check(rspe.Message, check.Matches, tc.message) 206 } 207 } 208 209 func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) { 210 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 211 c.Assert(err, check.IsNil) 212 213 rsp := s.syncReq(c, req, nil) 214 c.Assert(rsp.Status, check.Equals, 200) 215 res := rsp.Result.([]daemon.ValidationSetResult) 216 c.Check(res, check.HasLen, 0) 217 } 218 219 func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) { 220 st := s.d.Overlord().State() 221 st.Lock() 222 s.mockValidationSetsTracking(st) 223 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 224 as := s.mockAssert(c, "foo", "9") 225 err := assertstate.Add(st, as) 226 c.Check(err, check.IsNil) 227 as = s.mockAssert(c, "baz", "2") 228 err = assertstate.Add(st, as) 229 st.Unlock() 230 c.Assert(err, check.IsNil) 231 232 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 233 c.Assert(err, check.IsNil) 234 rsp := s.syncReq(c, req, nil) 235 c.Assert(rsp.Status, check.Equals, 200) 236 res := rsp.Result.([]daemon.ValidationSetResult) 237 c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{ 238 { 239 AccountID: s.dev1acct.AccountID(), 240 Name: "baz", 241 Mode: "monitor", 242 Sequence: 2, 243 Valid: false, 244 }, 245 { 246 AccountID: s.dev1acct.AccountID(), 247 Name: "foo", 248 PinnedAt: 9, 249 Mode: "enforce", 250 Sequence: 99, 251 Valid: false, 252 }, 253 }) 254 } 255 256 func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) { 257 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 258 return nil, &asserts.NotFoundError{ 259 Type: assertType, 260 } 261 } 262 263 st := s.d.Overlord().State() 264 st.Lock() 265 as := s.mockAssert(c, "baz", "2") 266 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 267 s.mockValidationSetsTracking(st) 268 st.Unlock() 269 270 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/baz", s.dev1acct.AccountID()), nil) 271 c.Assert(err, check.IsNil) 272 273 rsp := s.syncReq(c, req, nil) 274 c.Assert(rsp.Status, check.Equals, 200) 275 res := rsp.Result.(daemon.ValidationSetResult) 276 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 277 AccountID: s.dev1acct.AccountID(), 278 Name: "baz", 279 Mode: "monitor", 280 Sequence: 2, 281 Valid: false, 282 }) 283 } 284 285 func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) { 286 q := url.Values{} 287 q.Set("sequence", "9") 288 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/foo?%s", s.dev1acct.AccountID(), q.Encode()), nil) 289 c.Assert(err, check.IsNil) 290 291 st := s.d.Overlord().State() 292 st.Lock() 293 as := s.mockAssert(c, "foo", "9") 294 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 295 s.mockValidationSetsTracking(st) 296 st.Unlock() 297 c.Assert(err, check.IsNil) 298 299 rsp := s.syncReq(c, req, nil) 300 c.Assert(rsp.Status, check.Equals, 200) 301 res := rsp.Result.(daemon.ValidationSetResult) 302 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 303 AccountID: s.dev1acct.AccountID(), 304 Name: "foo", 305 PinnedAt: 9, 306 Mode: "enforce", 307 Sequence: 99, 308 Valid: false, 309 }) 310 } 311 312 func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) { 313 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 314 return nil, &asserts.NotFoundError{ 315 Type: assertType, 316 } 317 } 318 319 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 320 c.Assert(err, check.IsNil) 321 322 st := s.d.Overlord().State() 323 st.Lock() 324 s.mockValidationSetsTracking(st) 325 st.Unlock() 326 327 rspe := s.errorReq(c, req, nil) 328 c.Assert(rspe.Status, check.Equals, 404) 329 c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found") 330 c.Check(rspe.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 rspe := s.errorReq(c, req, nil) 590 c.Assert(rspe.Status, check.Equals, 404) 591 c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found") 592 c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{ 593 "account-id": "foo", 594 "name": "bar", 595 "sequence": 333, 596 }) 597 } 598 599 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) { 600 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 601 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 602 c.Assert(name, check.Equals, "bar") 603 c.Assert(sequence, check.Equals, 99) 604 c.Assert(pinned, check.Equals, true) 605 c.Assert(opts, check.NotNil) 606 c.Check(opts.AllowLocalFallback, check.Equals, true) 607 608 db := assertstate.DB(st) 609 headers, err := asserts.HeadersFromPrimaryKey(asserts.ValidationSetType, []string{release.Series, accountID, name, fmt.Sprintf("%d", sequence)}) 610 c.Assert(err, check.IsNil) 611 // validation set assertion available locally 612 vs, err := db.Find(asserts.ValidationSetType, headers) 613 c.Assert(err, check.IsNil) 614 return vs.(*asserts.ValidationSet), true, nil 615 }) 616 defer restore() 617 618 st := s.d.Overlord().State() 619 620 st.Lock() 621 vs := s.mockAssert(c, "bar", "99") 622 // add validation set assertion to the local db 623 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs) 624 st.Unlock() 625 626 body := `{"action":"apply","mode":"monitor", "sequence":99}` 627 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 628 c.Assert(err, check.IsNil) 629 630 rsp := s.syncReq(c, req, nil) 631 c.Assert(rsp.Status, check.Equals, 200) 632 633 var tr assertstate.ValidationSetTracking 634 635 // verify tracking information 636 st.Lock() 637 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 638 st.Unlock() 639 c.Assert(err, check.IsNil) 640 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 641 Mode: assertstate.Monitor, 642 AccountID: s.dev1acct.AccountID(), 643 Name: "bar", 644 PinnedAt: 99, 645 Current: 99, 646 LocalOnly: true, 647 }) 648 } 649 650 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedUnresolved(c *check.C) { 651 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 652 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 653 c.Assert(name, check.Equals, "bar") 654 c.Assert(sequence, check.Equals, 99) 655 c.Assert(pinned, check.Equals, true) 656 657 snaps := []interface{}{map[string]interface{}{ 658 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 659 "name": "snap-b", 660 "presence": "required", 661 "revision": "1", 662 }} 663 headers := map[string]interface{}{ 664 "authority-id": s.dev1acct.AccountID(), 665 "account-id": s.dev1acct.AccountID(), 666 "name": "bar", 667 "series": "16", 668 "sequence": "99", 669 "revision": "5", 670 "timestamp": "2030-11-06T09:16:26Z", 671 "snaps": snaps, 672 } 673 // validation set assertion coming from the store 674 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 675 c.Assert(err, check.IsNil) 676 return vs.(*asserts.ValidationSet), false, nil 677 }) 678 defer restore() 679 680 st := s.d.Overlord().State() 681 682 st.Lock() 683 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 684 st.Unlock() 685 686 body := `{"action":"apply","mode":"monitor", "sequence":99}` 687 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 688 c.Assert(err, check.IsNil) 689 690 rsp := s.syncReq(c, req, nil) 691 c.Assert(rsp.Status, check.Equals, 200) 692 693 var tr assertstate.ValidationSetTracking 694 695 // verify tracking information 696 st.Lock() 697 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 698 st.Unlock() 699 c.Assert(err, check.IsNil) 700 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 701 Mode: assertstate.Monitor, 702 AccountID: s.dev1acct.AccountID(), 703 Name: "bar", 704 PinnedAt: 99, 705 Current: 99, 706 }) 707 } 708 709 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeUnpinnedRefreshed(c *check.C) { 710 snaps := []interface{}{map[string]interface{}{ 711 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 712 "name": "snap-b", 713 "presence": "required", 714 "revision": "1", 715 }} 716 717 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 718 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 719 c.Assert(name, check.Equals, "bar") 720 c.Assert(sequence, check.Equals, 0) 721 c.Assert(pinned, check.Equals, false) 722 723 // new sequence 724 headers := map[string]interface{}{ 725 "authority-id": s.dev1acct.AccountID(), 726 "account-id": s.dev1acct.AccountID(), 727 "name": "bar", 728 "series": "16", 729 "sequence": "2", 730 "revision": "1", 731 "timestamp": "2030-11-06T09:16:26Z", 732 "snaps": snaps, 733 } 734 // updated validation set assertion coming from the store 735 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 736 c.Assert(err, check.IsNil) 737 return vs.(*asserts.ValidationSet), false, nil 738 }) 739 defer restore() 740 741 st := s.d.Overlord().State() 742 743 st.Lock() 744 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 745 st.Unlock() 746 747 headers := map[string]interface{}{ 748 "authority-id": s.dev1acct.AccountID(), 749 "account-id": s.dev1acct.AccountID(), 750 "name": "bar", 751 "series": "16", 752 "sequence": "1", 753 "revision": "1", 754 "timestamp": "2030-11-06T09:16:26Z", 755 "snaps": snaps, 756 } 757 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 758 c.Assert(err, check.IsNil) 759 760 st.Lock() 761 // add validation set assertion to the local db 762 c.Assert(assertstate.Add(st, vs), check.IsNil) 763 st.Unlock() 764 765 body := `{"action":"apply","mode":"monitor"}` 766 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 767 c.Assert(err, check.IsNil) 768 769 rsp := s.syncReq(c, req, nil) 770 c.Assert(rsp.Status, check.Equals, 200) 771 772 var tr assertstate.ValidationSetTracking 773 774 // verify tracking information 775 st.Lock() 776 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "bar", &tr) 777 st.Unlock() 778 c.Assert(err, check.IsNil) 779 c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{ 780 Mode: assertstate.Monitor, 781 AccountID: s.dev1acct.AccountID(), 782 Name: "bar", 783 Current: 2, 784 }) 785 } 786 787 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) { 788 restore := daemon.MockValidationSetAssertionForMonitor(func(st *state.State, accountID, name string, sequence int, pinned bool, userID int, opts *assertstate.ResolveOptions) (*asserts.ValidationSet, bool, error) { 789 return nil, false, fmt.Errorf("boom") 790 }) 791 defer restore() 792 793 body := `{"action":"apply","mode":"monitor"}` 794 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 795 c.Assert(err, check.IsNil) 796 797 rspe := s.errorReq(c, req, nil) 798 c.Assert(rspe.Status, check.Equals, 400) 799 c.Check(rspe.Message, check.Equals, fmt.Sprintf(`cannot get validation set assertion for %s/bar: boom`, s.dev1acct.AccountID())) 800 } 801 802 func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) { 803 st := s.d.Overlord().State() 804 805 for i, sequence := range []int{0, 9} { 806 st.Lock() 807 s.mockValidationSetsTracking(st) 808 st.Unlock() 809 810 var body string 811 if sequence != 0 { 812 body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence) 813 } else { 814 body = `{"action":"forget"}` 815 } 816 817 var tr assertstate.ValidationSetTracking 818 819 st.Lock() 820 // sanity, it exists before removing 821 err := assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 822 st.Unlock() 823 c.Assert(err, check.IsNil) 824 c.Check(tr.AccountID, check.Equals, s.dev1acct.AccountID()) 825 c.Check(tr.Name, check.Equals, "foo") 826 827 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 828 c.Assert(err, check.IsNil) 829 rsp := s.syncReq(c, req, nil) 830 c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i)) 831 832 // after forget it's removed 833 st.Lock() 834 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 835 st.Unlock() 836 c.Assert(err, check.Equals, state.ErrNoState) 837 838 // and forget again fails 839 req, err = http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 840 c.Assert(err, check.IsNil) 841 rspe := s.errorReq(c, req, nil) 842 c.Assert(rspe.Status, check.Equals, 404, check.Commentf("case #%d", i)) 843 } 844 } 845 846 func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) { 847 st := s.d.Overlord().State() 848 st.Lock() 849 s.mockValidationSetsTracking(st) 850 st.Unlock() 851 852 for i, tc := range []struct { 853 validationSet string 854 mode string 855 // sequence is normally an int, use string for passing invalid ones. 856 sequence string 857 message string 858 status int 859 }{ 860 { 861 validationSet: "0/zzz", 862 mode: "monitor", 863 message: `invalid account ID "0"`, 864 status: 400, 865 }, 866 { 867 validationSet: "Xfoo/bar", 868 mode: "monitor", 869 message: `invalid account ID "Xfoo"`, 870 status: 400, 871 }, 872 { 873 validationSet: "foo/Xabc", 874 mode: "monitor", 875 message: `invalid name "Xabc"`, 876 status: 400, 877 }, 878 { 879 validationSet: "foo/bar", 880 sequence: "x", 881 message: "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value", 882 status: 400, 883 }, 884 { 885 validationSet: "foo/bar", 886 mode: "bad", 887 message: `invalid mode "bad"`, 888 status: 400, 889 }, 890 // XXX: enable when enforcing is implemented. 891 { 892 validationSet: "foo/bar", 893 mode: "enforce", 894 message: `invalid mode "enforce"`, 895 status: 400, 896 }, 897 { 898 validationSet: "foo/bar", 899 sequence: "-1", 900 mode: "monitor", 901 message: `invalid sequence argument: -1`, 902 status: 400, 903 }, 904 } { 905 var body string 906 if tc.sequence != "" { 907 body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence) 908 } else { 909 body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode) 910 } 911 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body)) 912 c.Assert(err, check.IsNil) 913 rspe := s.errorReq(c, req, nil) 914 c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 915 c.Check(rspe.Message, check.Matches, tc.message) 916 } 917 } 918 919 func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) { 920 body := `{"action":"baz","mode":"monitor"}` 921 922 req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body)) 923 c.Assert(err, check.IsNil) 924 925 rspe := s.errorReq(c, req, nil) 926 c.Check(rspe.Status, check.Equals, 400) 927 c.Check(rspe.Message, check.Matches, `unsupported action "baz"`) 928 }