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